本篇主要涉及Android中UI開發和碎片的使用。
一、 UI開發
(一) 常見控件的使用
1. TextView
android:gravity
指定文字對其方式,可選值有top、bottom、left、right、center等。
2. Button
- 匿名類方式註冊監聽器
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 相應邏輯
}
});
- 實現接口方式註冊監聽器
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(bundle savedInstanceState) {
...
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
// 相應邏輯
break;
default:
break;
}
}
}
3. EditText
android:hint
設置提示性文本android:maxLines
設置最大行數獲取輸入的內容:
String inputText = editText.getText().toString();
4. ImageView
android:src="@drawable/img_1"
指定圖片- 動態更改圖片:
imageView.setImageResource(R.drawable.img_2);
5. ProgressBar
android:visibility
設置控件的可見屬性,默認visible
表示可見,invisible
表示不可見但佔據着原來的位置(透明狀態),gone
表示不可見且不佔用任何屏幕空間。- 通過代碼設置可見屬性:
if (progressBar.getVisibility() ==View.VISIBLE) {
progressBar.setVisibility(View.GONE);
}
- 通過style屬性可以指定不同樣式。
style="?android:attr/progressBarStyleHorizontal"
- 通過
android:max="100"
給進度條設置一個最大值,然後可在代碼中動態更改進度:
int progress = progressBar.getProgress();
progress = progress + 10;
progressBar.setProgress(progress);
6. AlertDialog
- 通過AlertDialog.Builder創建一個AlertDialog實例,然後可以設置標題、內容、可否取消等屬性,接下來調用
setPositiveButton()
方法爲對話框設置確定按鈕的點擊事件,調用setNegativeButton()
方法設置取消按鈕的點擊事件,最後調用show()
方法將對話框顯示出來。
AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
dialog.setTitle("這是標題");
dialog.setMessage("重要內容或警告信息");
dialog.setCancelable(false);
dialog.setPositiveButton("OK", new dialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
dialog.setNegativeButton("Cancel", new DialogInterface.OnClickLister() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
dialog.show();
7. ProgressDialog
- 與AlertDialog類似,也是構建一個ProgressDialog對象,然後設置標題、內容、可否取消等,最後通過
show()
方法顯示出來。 - 調用
dismiss()
方法關閉對話框;
(二) 四種基本佈局
1. 線性佈局 (LinearLayout)
android:orientation
指定排列方向,可選vertical或horizontal;android:layout_gravity
指定控件在佈局中的對齊方式,可選值有top、bottom、center_viertical、left、right等;android:layout_weight
可以按比例控制控件大小;
2. 相對佈局 (RelativeLayout)
android:layout_alignParentLeft
、android:layout_alignParentRight
、android:layout_alignParentTop
、android:layout_alignParentBottom
、
android:layout_centerInParent
屬性設置控件在父控件中的位置。android:above
、android:below
、android:toLeftOf
、android:toRightOf
設置控件間的相對位置。
3. 幀佈局 (FrameLayout)
- 所有控件默認擺放在佈局左上角,可使用
android:layout_gravity
設置對齊方式。
4. 百分比佈局
- 包括PercentFrameLayout和PercentRelativeLayout,包含在
com.android.support:percent
庫中。 app:layout_widthPercent
、app:layout_heightPercent
可直接指定控件在佈局中所佔百分比。
<Button
android:id="@+id/button1"
android:text="Button 1"
android:layout_gravity="left|top"
app:layout_widthPercent="50%"
app:layout_heightPercent="50%" />
(三) 自定義控件
1. 引入佈局
構建一個佈局文件,在主活動的佈局中使用 <include layout="@layout/title" />
引入。
2. 創建自定義控件
- 新建TitleLayout繼承自LinearLayout,重寫構造方法,藉助LayoutInflater對自定義佈局進行動態加載。
from(context)
方法構建出LayoutInflater對象inflate(要加載的佈局文件id, 父佈局)
進行動態加載
- 加入相應邏輯。
- 在佈局文件中添加,需要指明完整類名。
public class TitleLayout extends LinearLayout {
public TitleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.title, this);
Button titleBack = (Button) findViewById(R.id.title_back);
titleBack.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
((Activity) getContext()).finish();
});
}
}
<com.example.uicostomviews.TitleLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" />
(四) ListView
1. 基本用法
藉助適配器ArrayAdapter將數據傳遞給ListView:
- ArrayAdapter構造函數中傳入三個參數 (當前上下文、ListView子項佈局id、數據)。
- 調用ListView的
setAdapter()
方法將適配器傳入。
ArrayAdapter<String> adapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, data);
ListView listView = (ListView) findViewById(R.id.list_view);
listView.setAdapter(adapter);
2. 定製界面
(1) 定義一個實體類作爲Adapter的適配類型
public class Fruit {
private String name;
private int imageId;
public Fruit(String name, int imageId) {
this.name = name;
this.imageId = imageId;
}
public String getName() {
return name;
}
public int getImageId() {
return imageId;
}
}
(2) 爲ListView子項建立自定義佈局
<!-- fruit_item.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/fruit_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_verticlal"
android:layout_marginLeft="10dp" />
</LinearLayout>
(3) 創建自定義適配器
重寫構造函數和 getView()
方法,getView()會在每個子項滾動到屏幕內時被調用。
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId;
// 重寫構造函數
public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects) {
super(context, textViewResourceId, objects);
resourceId = textViewResourceId;
}
// 重寫getView()方法
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position); // 獲取當前項的Fruit實例
View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false); // 第三個參數表示只讓父佈局的Layout屬性生效,但不爲這個View添加父佈局
ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
fruitImage.setImageResource(fruit.getImageId());
fruitName.setText(fruit.getName());
return view;
}
(4) 在MainActivity中創建Adapter對象並傳遞給ListView
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
...
initFruit(); // 初始化水果數據的方法
FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList);
ListView listView = (ListView) findViewById(R.id.list_view);
listView.setAdapter(adapter);
}
private void initFruits() {
...
Fruit apple = new Fruit("Apple", R.drawable.apple_pic);
fruitList.add(apple);
...
}
}
3. 優化
- 解決重複加載佈局:
getView()
中convertView參數會將之前加載好的佈局進行緩存,便於之後重用。 - 解決重複findViewById獲取控件實例:新增內部類ViewHolder對控件實例進行緩存。
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position);
View view;
ViewHolder viewHolder;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
viewHolder = new ViewHolder;
viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name);
view.setTag(viewHolder); // 將ViewHolder存儲在View中
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag(); // 重新獲取ViewHolder
}
viewHolder.fruitImage.setImageResource(fruit.getImageId());
viewHolder.fruitName.setText(fruit.getName());
return view;
}
class ViewHolder {
ImageView fruitImage;
TextView fruitName;
}
4. 點擊事件
使用 setOnItemClickListener()
方法註冊監聽器,點擊子項時回調 onItemClick()
方法,重寫此方法加入點擊事件的處理邏輯。
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(adapterView<?> parent, View view, int position, long id) {
Fruit fruit =fruitList.get(position);
Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show();
}
});
(五) RecyclerView
1. 基本用法
- 新建適配器FruitAdapter繼承自RecyclerView.Adapter,並將泛型指定爲FruitAdapter.ViewHolder。
- 定義一個內部類ViewHolder繼承自Recycler.ViewHolder,構造函數傳入一個View參數,通過findViewById獲取佈局中控件實例;
- 適配器的構造函數用於將數據數據源傳進來;
- 重寫
onCreateViewHolder()
、onBindViewHolder()
、getItemCount()
這三個方法;
- MainActivity中創建LinearLayoutManager對象,並調用
setLayoutMangager()
設置到RecyclerView中,再創建適配器實例並調用setAdapter()
完成適配器設置。
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
private List<Fruit> mFruitList;
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView fruitImage;
TextView fruitName;
}
public ViewHolder(View view) {
super(view);
fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
fruitName = (TextView) view.findViewById(R.id.fruit_name);
}
public FruitAdapter(List<Fruit> fruitList) {
mFruitList = fruitList;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int ViewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
ViewHolder holder = new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Fruit fruit = mFruitList.get(position);
holder.fruitImage.setImageResource(fruit.getImageId());
holder.fruitName.setText(fruit.getName());
}
@Override
public int getItemCount() {
return mFruitList.size();
}
}
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protect void onCreate(Bundle savedInstanceState) {
...
initFruits(); // 初始化水果數據
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearManager(this);
recyclerView.setLayoutManager(layoutManager);
FruitAdapter adapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
}
private void initFruits(){
// 將水果數據添加到fruitList中
}
}
2. 橫向滾動、網格佈局和瀑布流佈局
(1) 橫向滾動
- 要實現橫向滾動,需把fruit_item裏的元素改爲垂直排列。
- 調用LinearLayoutManager的
setOrientation()
來設置佈局排列方向。
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
(2) 網格佈局
- 使用GridLayoutManager,構造函數接收兩個參數:(Context, 列數) 。
GridLayoutManager layoutManager = new GridLayoutManager(this, 2); // 兩列的網格佈局
recyclerView.setLayoutManager(layoutManager);
(3) 瀑布流佈局
- 使用StaggeredGridLayoutManager,構造函數接收兩個參數:(列數, 排列方向) 。
StaggeredGridLayoutManager layoutManager = new StaggeredLayoutManager(3, StaggeredGridLayoutManager.VERTICAl); // 三列、縱向排列
recyclerView.setLayoutManager(layoutManager);
3. 點擊事件
需要在 onCreateViewHolder()
中自己給子項具體的View去註冊點擊事件。
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHoder> {
...
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int ViewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
final ViewHolder holder = new ViewHolder(view);
holder.fruitImage.setOnclickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = holder.getAdapterPosition();
Fruit fruit = mFruitList.get(position);
Toast.makeText(v.getContext(), "點擊了圖片" + fruit.getName(), Toast.LENGTH_SHORT).show();
}
});
return holder;
}
...
}
二、 Fragment
(一) 碎片的使用
1. 基本用法
- 創建碎片的佈局文件;
- 新建類繼承自Fragment,重寫
onCreateView()
方法; - 在主活動的佈局文件中添加
<fragment>
標籤,需要通過android:name
指明添加的碎片完整類名(帶包名)。
public class MyFragment extends Fragment {
@Override
public View onCreateView(LayoutInflate inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.my_fragment, container, false);
return view;
}
}
<fragment
android:id="@+id/my_fragment"
android:name="com.example.fragmenttest.MyFragment"
android:layoutwidth="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
2. 動態添加碎片
(1) 創建待添加碎片實例;
(2) 調用getSupportFragmentManager()
方法獲取FragmentManager;
(3) 調用 beginTransaction()
開啓一個事務;
(4) 調用 replace()
方法向容器內添加或替換碎片,需傳入 (容器id, 碎片實例);
(5) 調用 commit()
方法提交事務。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
...
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
replaceFragment(new AnotherFragment()); // 替換爲另一個碎片
}
});
replaceFragment(new MyFragment()); // 初始設置爲MyFragment
}
private void repalceFragment(Fragment fragment) {
FragmentManager fragmentmanamger = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.repalce(R.id.right_layout, fragment);
transaction.commit();
}
}
3. 碎片中模擬返回棧
調用 addToBackStack()
方法將事務添加到返回棧中,它接收一個名字用於描述返回棧狀態,一般傳入null即可,這樣按下Back鍵會回到上一個Fragment界面。
FragmentManager fragMentmanager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.right_layout, fragment);
transaction.addToBackStack(null); // 添加到返回棧
transaction.commit();
4. 碎片和活動間進行通信
- 活動中調用碎片裏的方法:使用FragmentManager的
findFragmentById
獲取碎片實例,即可調用其中方法。 - 碎片中調用活動裏的方法:使用
getActivity()
得到和當前碎片關聯的活動實例,即可調用其中方法。
MyFragment myFragment = (MyFragment) getFragmentManager().findFragmentById(R.id.my_fragment); // 活動中獲取碎片實例
MainActivity activity = (MainActivity) getActivity(); // 碎片中獲取活動實例
(二) 碎片的生命週期
與活動生命週期類似,並提供了一些的附加的回調方法:
onAttach()
:當碎片和活動建立關聯時調用。onCreateView()
:爲碎片創建視圖(加載佈局)時調用。onActivityCreated()
:確保與碎片關聯的活動一定已創建完畢時調用。onDestroyView()
:當與碎片關聯的視圖被移除時調用。onDetach()
:當碎片和活動解除關聯時調用。
碎片中也可以通過 onSaveInstanceState()
方法保存數據,保存的數據在 onCreate()
、onCreateView()
、onActivityCreated()
中都可以得到。
(三) 動態加載佈局技巧
1. 使用限定符
- 屏幕大小:small、normal、large、xlarge
- 屏幕分辨率:ldpi、mdpi、hdpi、xhdpi、xxhdpi
- 方向:land、port
2. 使用最小寬度限定符(Smallest-width Qualifier)
res目錄下新建layout-sw600dp文件夾,在其中新建acticity_main.xml佈局。
- 屏幕寬度大於600dp的設備:加載layout-sw600dp/activity_main佈局
- 屏幕寬度小於600dp的設備:仍會加載默認的layout/activity_main佈局。