====================================
====== 第三章:UI開發(fā)的點點滴滴 ======
====================================
界面設計和功能實現(xiàn)同樣重要。界面美觀可以增加用戶粘性养涮。
1罪针、如何編寫程序界面简卧。
我們不建議使用可視化見面來進行拖拽編寫布局熟呛,因為可視化編輯不利于你去真正了解界面背后的實現(xiàn)原理藕帜。通過可視化界面寫出的界面通常不具備很好的屏幕適配性。并且編寫復雜界面時無法勝任栓始。因此本書的所有界面都是通過編寫xml代碼來實現(xiàn)的胸完。
2书释、重用控件的使用方法:
新建一個UIWidgetTest項目。
2.1 TextView:聽說是最簡單的控件赊窥,顯示一段文本信息爆惧。修改activity_main.xml中代碼如下:
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:orientation=“vertical”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<TextView
android:id=“@+id/text_view”
android:layout_width=“match_parent”
android:layout_height=“wrap_context”
android:text=“This is TextView” />
</LinearLayout>
match_parent、fill_parent锨能、wrap_content都是控件的可選屬性扯再。其中芍耘,match_parant和fill_parent的意義相同,官方更加推薦match_parent熄阻。wrap_context表示讓當前控件的大小剛好可以包住里面的內容斋竞,也就是控件內容決定當前控件的大小。當然你也可以指定控件一個特定值的寬高的大小秃殉。(但是這樣可能會出現(xiàn)屏幕的適配問題)
如需修改textview里面的文字屬性坝初,增加android:textSize=“24sp” android:textColor=“#00ff00”
Android中字體大小使用sp作為單位。
3.2.2 Button
添加一個Button钾军,
<Button
android:id=“@+id/button”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
andoird:text=“Button” />
我們在布局文件里面設置的文字是Button鳄袍,但是最終顯示結果卻是BUTTON,這是由于系統(tǒng)會對button中的所有英文字母自動進行大寫轉換吏恭,如果這不是想要的效果拗小,可以使用如下配置來禁用這一默認特新:Android:textAllCaps=“false”
接著,在MainActivity中添加按鈕的點擊邏輯
Public class MainActivity extends AppCpmpatActivity {
@ovrride
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.onClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button: {
// 點擊按鈕的邏輯
}break;
default:
break;
}
}
});
}
}
如果不喜歡使用匿名類的方式來注冊監(jiān)聽器砸泛,也可以使用實現(xiàn)接口的方式來進行注冊
Public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
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.2.3 EditText(輸入框)
一樣的十籍,android:hint=“placeholder text”, android:maxLines=“2”
3.2.4 ImageView
顯示圖片的控件,圖片通常都是放在drawable開頭的目錄下
創(chuàng)建一個drawable-xhdpi的文件夾唇礁,放入兩張圖片
<ImageView
android:id=“@+id/image_view”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:src=“@drawable/img_1” />
3.2.5 ProcessBar(中間圓圈進度旋轉)
<ProcessBar
android:id=“@+id/progress_bar”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
style=“?android:attr/progressBarStyleHorizontal”
android:max=“100” />
所有控件都可以設置一個屬性visibility,可選值為visible可見的(默認值)惨篱、invisible不可見和gone不僅不可見而且不占任何屏幕控件盏筐。我們還可以通過代碼來設置控件的可見性,使用的是視圖的setVisibility()砸讳,可以傳入View.VISIBLE琢融、View.INVISIBLE、View.GONE
if (progressBar.getVisibility() == View.GONE) {
progressBar.setVisibility(View.VISIBLE);
} else {
progressBar.setVisibility(View.GONE);
}
// 當修改了progressBar的樣式之后簿寂,修改如下代碼漾抬,就可以點擊button的時候讓progress一點點增加
int progress = progressBar.getProgress();
progress += 10;
progressBar.setProgress(progress);
3.2.6 AlertDialog
AlertDialog可以在當前界面彈出一個對話框,并且是置頂于所有控件之上常遂,能夠屏蔽掉其他控件的交互能力纳令,因此AlertDialog一般都是用于提示一些非常重要的內容或者警告信息。
我們現(xiàn)在可以在MainActivity中寫上如下代碼克胳。
public void onClick(View v) {
switch (v.getId()) {
case R.id.button: {
AlertDialog.Bulder dialog = new AlertDialog.Bulder(MainActivity.this);
dialog.setTitle(“This is Dialog”);
dialog.setMessage(“Something important.”);
dialog.setCancelable(false);
dialog.setPositiveButton(“OK”, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
dialog.setNegativeButton(“Cancel”, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
dialog.show();
break;
default:
break;
}
}
}
setPositiveButton()方法為對話框設置確定按鈕點擊事件平绩,setNegativeButton()方法設置取消按鈕點擊事件。最后調用show()方法顯示出來漠另,
3.2.7 ProgressDialog(貌似現(xiàn)在已經取消使用了)
與AlertDialog類似捏雌,但是會有一個進度條,一般表示當前操作比較耗時笆搓,讓用戶耐心等待
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
…
@Override
public void onClick(View v) {
Switch (v.getId()) {
case R.id.button: {
ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
progressDialog.setTitle(“This is ProgressDialog”);
progressDialog.setMessage(“Loading…”);
progressDialog.setCancelable(ture);
progressDialog.show();
}break;
default:
break;
}
}
設置setCancelable()傳入false性湿,表示不能通過back鍵取消掉纬傲。
3.3 詳解4種基本布局。(新的測試項目UILayoutTest)
布局的內部除了放置控件外肤频,還可以放置布局嘹锁,通過多層布局的嵌套,我們就能夠完成一些比較復雜的界面實現(xiàn)着裹。
Android中包括四種布局:線性布局领猾、相對布局、幀布局骇扇、百分比布局
3.3.1 線性布局
線性布局LinearLayout摔竿,將他所包含的控件在線性方向上依次排列。之前我們用的一直都是這個線性布局少孝,可以通過修改adroid:orientation屬性指定排列方向是vertival為垂直方向继低,horizontal為水平方向。
<LinearLayout xmlns:android:=“http://schemas.android.com/apk/res/android”
android:orientation=“vertical”/horizontal
android:layout_width=“match_parent”
android:layout_height=“match_parent” >
</ LinearLayout>
需要注意的是稍走,如果排列方式設置為horizontal水平方向袁翁,內部的控件就絕對不能將寬度指定為match_parent,因為這樣的話婿脸,單獨一個控件就占滿整個水平方向了粱胜,其他控件就沒有位置可以放置了。同樣道理狐树,如果排列方向設置為vertival垂直方向焙压,那么內部控件的高度就不能設置為match_parent。
android:layout_gravity屬性用于指定文字在控件中的對齊方式抑钟。
Android:layout_weight使用比例的方式來指定控件的大醒那(在手機屏幕的適配性方便非常重要)
案例:
<EditText
android:id=“@+id/input_message”
android:layout_width=“0dp”
android:layout_height=“wrap_content”
android:layout_weight=“1”
android:hint=“Type something” />
<Button
android:id=“@+id/send”
android:layout_width=“0dp”
android:layout_height=“wrap_content”
android:layout_weight=“1”
android:text=“Send” />
這里雖然設置了android:layout_width=“0dp”,但是由于設置了比重在塔,以比重為準幻件,0dp是比較標準的寫法。
比重就是總數(shù)加起來蛔溃,然后再根據(jù)占的比重來決定寬度绰沥,現(xiàn)在很明顯是EditText和Button平分寬度。
這時候如果Button的layout_width改為wrap_content城榛,然后EditText保持1的比比重不變揪利,那么Button就會保持固定大小,然后EditText占據(jù)剩余的寬度空間狠持。
3.3.2 相對布局
相對布局RelativeLayout疟位,相對隨意,它可以通過相對定位的方式讓控件出現(xiàn)在布局的任何位置喘垂。因此甜刻,屬性非常多绍撞,不過這些屬性都是有規(guī)律可循。
如案例:5個按鈕得院,分別在左上傻铣,右上,中間祥绞,左下非洲,右下。
<RelativeLayout xmlns:android:”http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent” >
<Button
android:id=“@+id/button1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentLeft=“true”
android:layout_alignParentTop=“true”
android:text=“Button 1” />
<Button
android:id=“@+id/button2”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentRight=“true”
android:layout_alignParentTop=“true”
android:text=“Button 2” />
<Button
android:id=“@+id/button3”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_centerInParent=“true”
android:text=“Button 3” />
<Button
android:id=“@+id/button4”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentLeft=“true”
android:layout_alignParentBottom=“true”
android:text=“Button 4” />
<Button
android:id=“@+id/button5”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentRight=“true”
android:layout_alignParentBottom=“true”
android:text=“Button 5” />
</RelativeLayout>
上面是相對于父控件進行定位的蜕径。
如果使用相對于其他控件的相對布局两踏,用下面的方式
android:layout_above=“@id/button3” 上方
android:layout_toRightOf=“@id/button3” 右側
android:layout_toLeftOf=“@id/button3” 左側
android:layout_below=“@id/button3” 下側
android:layout_alignLeft 讓一個控件的左邊緣和另一個控件的左邊緣對齊
android:layout_alignRight 讓一個控件的右邊緣和另一個控件的右邊緣對齊
android:layout_alignTop 上邊緣對齊
android:layout_alignBottom 下邊緣對齊
3.3.3 幀布局
幀布局FrameLayout,相對其他兩種布局簡單多了兜喻,應用場景也少了很多梦染。
這種布局沒有方便的定位方式,所有的控件都會默認擺放在布局的左上角
<FrameLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent” >
<TextView
android:id=“@+id/text_view”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=“This is TextView” />
<ImageView
android:id=“@+id/image_view”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:src=“@mipmap/ic_launcher” />
</FrameLayout>
FrameLayout還可以使用layout_gravity屬性
Android:layout_gravity=“l(fā)eft”
Android:layout_gravity=“right”
由于FrameLayout的定位方式的缺失朴皆,導致它的使用場景比較少帕识。
3.3.4 百分比布局
前面3種布局都是從android 1.0版本就開始支持了,一直沿用到現(xiàn)在遂铡。這種布局中肮疗,我們可以不再使用wrap_content、match_parent等方式來指定控件的大小忧便,而是允許直接指定控件在布局中所占的百分比族吻。
百分比布局只為FrameLayout和RelativeLayout進行了功能拓展,提供了PercentFrameLayout和PercentRelativeLayout這兩種全新的布局珠增。
怎樣做到新增的百分比布局在所有的android版本中都能使用呢。為此砍艾,android團隊將百分比布局定義在support庫當中蒂教。我們只需要在項目中build.gradle中添加百分比布局庫的以來,就能保證百分比布局在android所有系統(tǒng)版本上的兼容性了脆荷。
修改app/build.gradle文件中
Dependencies {
implementation ‘com.android.support:percent:28.0.0’
}
如果發(fā)現(xiàn)28.0.0版本不夠新凝垛,系統(tǒng)會提示你修改。修改完之后還需要重新build一下蜓谋。(點擊Sync Now)
現(xiàn)在使用PercentFrameLayout
app:layout_widthPercent=“50%” // 將寬度指定為布局的50%
app:layout_heightPercent=“50%” // 將高度指定為布局的50%
PercentFrameLayout還是會繼承FrameLayout的特性梦皮,即所有的控件默認都擺放在布局的左上角,為了讓四個按鈕不會重疊桃焕,還是接住了layout_gravity分別將4個按鈕放置在左上剑肯、右上、左下观堂、右下四個位置让网。android:layout_gravity="left|bottom"
PercentRelativeLayout的用法類似呀忧,也是繼承了RelativeLayout的所有屬性,然后再結合app:layout_widthPercent和app:layout_heightPercent來按百分比指定控件的寬高
3.4 創(chuàng)建自定義控件
所有控件都是繼承自View溃睹,所有的布局都是直接或者間接繼承自ViewGroup的而账。
創(chuàng)建一個新的應用UICustomViews
單獨創(chuàng)建一個title.xml,用于設定類似iOS的頂部導航欄因篇,然后在activity_main.xml中添加<include layout=“@layout/title” />
android:background=“@drawable/title_bg.png” // 用于指定背景圖片
Android:layout_marginLeft,layout_marginRight,layout_mariginTop,layout_marginBottom用于指定上下左右偏移的距離
android:background可以指定一個背景圖片.
android:layout_margin屬性可以指定控件在上下左右方向上偏移的距離泞辐,當然也可以使用
android:layout_marginLeft或者android:layout_marginTop等屬性來單獨指定控件在某個方向上便宜的距離。
接下來修改activity_main.xml
加上<include layout=“@layout/title” />
最后在MainActivity中將系統(tǒng)自帶的標題欄隱藏掉
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main(;
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.hide();
}
}
}
ActionBar就是我們的導航欄
隱藏標題欄之后竞滓,我們自己寫的tilte.xml就生效了咐吼。以后只要一句include就可以包含進來了。
3.4.2 創(chuàng)建自定義控件
新建TitleLayout繼承自LinearLayout虽界,讓它成為我們自定義的標題欄控件
public class TitleLayout extends LinearLayout {
public TitleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.title, this);
}
}
在布局中引入TitleLayout控件就會調用這個構造函數(shù)汽烦,然后在構造函數(shù)中需要對標題欄布局進行動態(tài)加載,這就要借助LayoutInflater來實現(xiàn)莉御。通過LayoutInflater的from()方法可以構建出一個ayoutInflater對象撇吞,然后調用inflate()方法就可以動態(tài)加載一個布局文件,inflate()方法兩個參數(shù)礁叔,第一個是加載的布局文件的id牍颈,這里傳入R.layout.title,第二個參數(shù)是給加載好的布局再添加一個父布局琅关,這里我們想要指定為TitleLayout煮岁,于是直接傳入this。
現(xiàn)在這個自定義控件創(chuàng)建好了涣易,然后我們需要在布局文件中添加這個自定義控件画机,修改activity_main.xml中的代碼,如下所示:
<LinearLayout xmlns:android=“http”//schemas.android.com/apk/res/android”
android:layout_width=“match_parant”
android:layout_height=“match_parent” >
// 包名在這里是不可以省略的
<com.example.uicustomviews.TitleLayout
android:layout_width=“match_parent”
android:layout_height=“wrap_content” />
</LinearLayout>
下面我們嘗試為標題欄中的按鈕注冊點擊事件新症,修改TitleLayout中的代碼步氏,如下
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);
Button titleEdit = (Button) findViewById(R.id.title_edit);
titleBack.setOnclickListener(new OnClickListener() {
@Override
public void onClick(View v) {
((Activity) getContext()).finish();
}
});
titleEdit.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(), “You click Edit button”, Toast.LENGTH_SHORT).show();
}
});
}
}
首先還是通過findViewById()方法得到按鈕的實例,然后分別調用setOnclickListenner()方法給兩個按鈕注冊了點擊事件徒爹,
當點擊返回按鈕的時候銷毀當前的活動
當點擊編輯按鈕的時候彈出一段文本荚醒。
3.5 最常用和最難用的空控件 —》 ListView(相當于iOS中的UITableView)
3.5.1 LIstView的簡單用法
新建一個項目ListViewTest項目,然后修改activity_main.xml中的代碼
<LinearLayout xmlns:android=android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent” >
<ListView android:id=“@+id/list_view”
android:layout_width=“match_parent”
android:layout_height=“match_parent” />
</LinearLayout>
接下來修改MainActivity的代碼
public class MainActivity extends AppCompatActivity {
private String[] data = {
“Apple”,”Banana”,”O(jiān)range”,”watermelon”,”Pear”,”Grape”,”Pineapple”,”Strawberry”,”Cherry”,”Mango”,
“Apple”,”Banana”,”O(jiān)range”,”watermelon”,”Pear”,”Grape”,”Pineapple”,”Strawberry”,”Cherry”,”Mango”
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
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);
}
}
我們需要通過借助適配器來把數(shù)據(jù)傳遞給ListView隆嗅,Android中提供了很多適配器的實現(xiàn)類界阁,其中我認為最好用的就是ArrayAdapter。它可以通過泛型來指定要適配的數(shù)據(jù)類型胖喳,然后在構造函數(shù)中把要適配的數(shù)據(jù)傳入泡躯。ArrayAdater有多個構造函數(shù)的重載,你應該根據(jù)實際情況選擇最合適的一種。上面在ArrayAdapter的構造函數(shù)中依次穿日當前上下文精续、ListView子項布局的id坝锰,以及要適配的數(shù)據(jù)。注意重付,我們使用了android.R.layout.simple_list_item_1作為子項布局的id顷级,這是一個Android內置的布局文件,里面只有一個TextView确垫,可用于簡單的顯示一段文本弓颈。
3.5.2 定制ListView的界面
只顯示一段文本的LIstView太單調了,現(xiàn)在我們來對ListView的界面進行定制删掀。
首先我們需要準備好一組圖片翔冀,分別對應提供的每一種水果。
接著定義一個實體類披泪,作為ListView適配器的適配類型纤子。新建Fruit類,代碼如下:
public class Fruit {
private String name;
private int imageId;
public Fruit(String name, int imageId) {
this.name = name; // 水果名
this.imageId = imageId; // 水果資源id
}
public String getName() {
return name;
}
public int getImageId() {
return imageId;
}
}
然后我們?yōu)長istVie的子項指定一個我們自定義的布局款票,在layout目錄下新建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/fruite_image”
android:layout_width=“wrap_content”
android:laytout_height=“wrap_content” />
<TextView
android:id=“@+id/fruit_name”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_gravity=“center”
android:layout_marginLeft=“10dp” />
</LinearLayout>
上面的代碼中,我們定義了一個ImageView用于顯示水果的圖片艾少,又定義了一個TextView用于顯示水果的名稱卡乾,并讓TextView在垂直方向上居中顯示。
接下來我們創(chuàng)建一個自定義的適配器缚够,這個適配器繼承自ArrayAdapter幔妨,并將泛型指定為Fruit類,新建類FruitAdapter:
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId; // 圖片資源id
public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects) {
super(context, textViewResourceId, objects);
resourceId = textViewResoureceId;
}
@Override
public View getView(int positon, VIew convertView, ViewGroup parent) {
Fruit fruit = getItem(position); // 獲取當前項的Fruit實例
View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, flase);
ImageView fruitImage = (ImageView) findViewById(R.id.fruit_image);
TextView fruitName = (TextView) findViewById(R.id.fruit_name);
fruitImage.setImageResource(fruit.getImageId());
fruitName.setText(fruit.getName());
return View;
}
}
FruitAdapter重寫了父類的一組構造函數(shù)谍椅,用于將上下文误堡,ListView子項布局的id和數(shù)據(jù)傳遞過去。另外又重寫了getVIew()方法雏吭,這個方法在每一個子項被滾動到屏幕內的時候會被調動埂伦。在getView()方法中,首先通過getItem()方法得到當前項的Fruit實例思恐,然后使用LayoutInflater來為這個子項加載我們傳入的布局。
這里的Layouinflater的inflater()方法接收3個參數(shù)膊毁,前面兩個參數(shù)我們都知道是什么意思了胀莹,第三個參數(shù)指定成flase,表示只讓我們在父布局中聲明的layout屬性生效婚温,但不為這個View添加父布局描焰,因為一旦View有了父布局之后,它就不能再天機到ListView中了。(現(xiàn)在不太明白沒關系荆秦,這其實是ListView中的標準寫法)
最后我們修改MainActivity中的代碼篱竭,
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>(); // 水果數(shù)據(jù)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState) ;
setContentView(R.layout.activity_main);
initFruits(); // 初始化水果數(shù)據(jù)
FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList);
ListView listView = (ListView) findViewById(R.id.list_view);
listView.setAdapter(adapter);
}
private void initFruit() {
for (int i = 0; i < 2; i++) {
Fruit apple = new Fruit(“Apple”, R.drawable.apple_pic);
fruitList.add(apple);
Fruit banana = new Fruit(“Banana”, R.drawable.apple_pic);
fruitList.add(banana);
…
}
}
}
只要修改fruit_item.xml中的內容,就可以定制出各種復雜的界面了步绸。
3.5.3 提升ListView的運行效率
目前我們的LIstView的運行效率是很低的掺逼,因為在FruitAdapter的getView()方法中,每次都將布局重新加載了一遍瓤介,當ListView快速滾動的時候吕喘,這就會成為性能的瓶頸。
仔細觀察刑桑,getView()方法還有一個converView的參數(shù)氯质,這個參數(shù)用于將之前加載好的布局進行緩存,以便之后可以進行重用祠斧。修改FruitAdapter中的代碼闻察,如下
public class FruitAdapter extends ArrayAdapter<Fruit> {
…
@Override
public View getView(int positon, View convertView, VIewGroup parent) {
Fruit fruit = getItem(position);
View view;
if (convertView == null) {
view = LayoutFlater.from(getContext()).inflate(resourceId, parent, false);
} else {
view = convertView;
}
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;
}
}
雖然現(xiàn)在已經不會重復加載布局,但是每次在getView()的時候琢锋,還是會調用View的findViewById()方法來獲取一次控件的實例辕漂。繼續(xù)優(yōu)化,使用ViewHolder來使得不會重復創(chuàng)建吩蔑。
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position);
View view;
ViewHolder viewHolder;
if (convertVIew == null) {
view = LayoutFlater.from(getContext()).inflate(resourceId, parent, false);
viewHolder = new ViewHolder();
veiwHolder.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.fruitImage.setImageResource(fruit.getImageId());
viewHolder.fruitNma.setText(fruit.getName());
return view;
}
class ViewHolder {
ImageView fruitImage;
TextView fruitName;
}
3.5.4 ListView的點擊事件
修改MainActivity中的代碼
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();
}
});
3.6 更強大的滾動控件 — RecyclerView
ListView雖然很好用,擴展性不好烛芬,只能實現(xiàn)縱向的滾動效果隧期,而且如果我們不使用一些技巧來提升它的運行效率,那么LIstView的性能就會非常差赘娄。
RecyclerView是增強版的ListView仆潮,可以輕松實現(xiàn)ListView的效果,還優(yōu)化了ListView的各種不足之處遣臼。目前Android更推薦使用RecyclerView性置。
新建一個RecyclerViewTest的項目
android團隊將RecyclerView定義在support庫當中,想要使用RecyclerVIew這個控件揍堰,需要再項目中添加相應的依賴庫才行鹏浅。
打開app/build.gradle文件,在dependencies中添加如下內容
dependencies {
compile fileTree(dir: ‘libs’, include: [‘*.jar’])
compile ‘com.android.support:appcompat-v7:24.2.1’
compile ‘com.android.support:recyclerview-v7:24.2.1’
testCompile ‘junit:junit:4.12’
}
Warming:
新版本配置如下:recyclerview的版本應該和appcompat的版本保持一致屏歹。
dependencies {
implementation fileTree(dir: 'libs', include: ['.jar'*])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.recyclerview:recyclerview:1.1.0-beta02'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
添加完之后點擊Sync Now來進行同步一下隐砸,然后修改activity_main.xml中的代碼,
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent” >
// 由于RecyclerView并不是在系統(tǒng)SDK中蝙眶,所以需要把完整的包名路徑寫出來
<android.support.v7.widget.RecyclerView
android:id=“@+id/recycler_view”
android:layout_width=“match_parent”
android:layout_height=“match_parent” />
</LinearLayout>
為了使得RecyclerView達到和ListView相同的效果季希。我們需要把圖片。Fruit類,fruit_item.xml復制過來式塌。免得再寫一遍博敬。
新建一個新的FruitAdapter,繼承自RecyclerVIew.Adapter峰尝,并將泛型指定為FruitAdapter.ViewHolder
偏窝。。境析。
3.6.2 RecyclerView實現(xiàn)橫向滾動和瀑布流布局
首先對fruit_item.xml進行修改囚枪,因為這個布局目前是水平排列的。
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:orientation=“vertical”
android:layout_width=“100dp”
android:layout_height=“wrap_content” >
// 垂直方向排列
<ImageView android:id=“@+id/fruit_image”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_gravity=“center_horizontal” />
// 垂直方向排列
<TextView android:id=“@+id/fruit_name”
android:layout_width=“wrap_content”
android:layotu_height=“wrap_content”
android:layout_gravity=“center_hotizontal”
android:layout_marginTop=“10dp” />
</LinearLayout>
現(xiàn)在修改MainActivity的代碼
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_main);
initFruit();
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientaion(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(layoutManager);
FruitAdapter adapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
}
}
調用LinearLayoutManger的setOrientation()方法來設置布局的排列方向劳淆,默認是縱向排列的链沼,我們傳入LinearLayoutManger.HORIZONTAL表示讓布局橫行排列。
ListView的布局排列是由自身去管理的沛鸵,而RecyclerView則將這個工作交給了LayoutManger括勺,LayoutManage中制定了一套殼拓展的布局排列接口,子類只要按照接口的規(guī)范來實現(xiàn)曲掰,就能制定出各種不同排列方式的布局了疾捍。
處理LinearLayoutManger,RecyclerView還給我們提供了GridLayoutManger和StaggeredGridLayoutManager這兩種內置的布局排列方式栏妖。GridLayoutManaget可以用于實現(xiàn)網格布局乱豆,StaggeredGridLayoutManager可以用于實現(xiàn)瀑布流布局。
現(xiàn)在我們來實現(xiàn)更加炫酷的瀑布流布局(相當于iOS中的UICollectionVeiw)
繼續(xù)修改fruit_item.xml
<LinearLayout xmlns:android=“http://schemas.adnroid.com/apk/res/android”
adnroid:orientation=“vertical”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:layout_margin=“5dp” >
<ImageView
android:id=“@+id/fruit_image”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_gravity=“center_horizontal” />
<TextView
android:id=“@+id/fruit_name”
android:layout_width=“wrap_contetn”
android:layout_height=“wrap_contetn”
android:layout_gravity=“Left”
android:layout_marginTop=“10dp” />
</LinearLayout>
這里做了一些小調整吊趾,首先將LinearLayout的寬度由100dp修改為match_parent宛裕,因為瀑布流布局的寬度應該是根據(jù)布局的列數(shù)來自動適配的。而不是一個固定的值论泛。另外我們使用了layout_maring屬性來讓子項之間互留一點間距揩尸。還有將TextView的對齊屬性改成了居左對齊。
接著修改MainAcitivity代碼:
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundel savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main):
initFruits();
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
// 構造函數(shù)接收兩個參數(shù)屁奏,3表示布局分為3列岩榆,第二個參數(shù)用于指定布局的排列方式。
StaggerdGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTCAL);
recyclerView.setLayoutManager(layoutManager);
FruitAdapter adapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
}
private void initFruit() {
for (int i = 0; i < 2; i++) {
Fruit apple = new Fruit(getRandomLengthName(“Apple”, R.drawable.apple_pic);
fruitList.add(apple);
…
}
}
private Strring getRandomLengthName(String name) {
Random random = new Random();
// 創(chuàng)建一個1-20的隨機數(shù)坟瓢,
int length = random.nextInt(20) + 1;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < length; i++ ) {
builder.append(name);
}
// 輸出字符串
return builder.toString();
}
}
3.6.3 RecyclerView的點擊事件
不同于ListView勇边,RecyclerView并沒有提供類似于setOnItemClickListener()這樣的注冊監(jiān)聽器方法。而是需要我們自己給子項具體的view去注冊點擊事件折联,相對于ListView來說粥诫,實現(xiàn)起來要復雜一點。
ListView的點擊事件上的處理并不人性化崭庸,setOnItemClickListener()方法注冊的是子項的點擊事件,但如果我們想點擊的是子項里面具體的某一個按鈕呢,ListView也能實現(xiàn)怕享,但是實現(xiàn)起來就相對比較麻煩了执赡。為此,RecyclerView干脆直接摒棄了子項點擊事件的監(jiān)聽器函筋,所有的點擊事件都由具體的View去注冊沙合,就再沒有這個困擾了。
修改FruitAdapter中的代碼:
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
private List<Fruit> mFruitList;
static class ViewHolder extends RecyclerView.ViewHolder {
View fruitView;
ImageView fruitImage;
TextView fruitName;
public ViewHolder(View view) {
super(view);
fruitView = 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);
final ViewHolder holder = new ViewHolder(view);
holder.fruitVeiw.setOnClickListener( new View.OnClickListener() {
@Ovrride
public void onClick(View v) {
int position = holder.getAdapterPosition();
Fruit fruit = mFruitList.get(position);
Toast.makeText(v.getContext(), “you clicked view” + fruit.getName(), Toast.LENTH_SHORT()).show();
}
)};
holder.fruitImage.setOnclickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = holder.getAdapterPositon();
Fruit fruit = mFruitList.get(position);
Toast.makeText(v.getContext(), “you clicked view” + fruit.getName(), Toast.LENTH_SHORT).show();
}
)};
return holder;
}
…
}
RecyclerView的強大之處在于跌帐,它可以輕松實現(xiàn)子項中任意控件或布局的點擊事件首懈。
3.7 編寫界面的最佳實踐。
現(xiàn)在我們結合之前所有的知識谨敛,來做衣蛾較為復雜且沒管的聊天界面究履。創(chuàng)建一個UIBestPractice項目。
具體代碼看Android Studio里面的代碼