Android第一行代碼讀書筆記 - 第三章

====================================

====== 第三章:UI開發的點點滴滴 ======

====================================

界面設計和功能實現同樣重要。界面美觀可以增加用戶粘性。

1、如何編寫程序界面。

我們不建議使用可視化見面來進行拖拽編寫布局,因為可視化編輯不利于你去真正了解界面背后的實現原理。通過可視化界面寫出的界面通常不具備很好的屏幕適配性。并且編寫復雜界面時無法勝任。因此本書的所有界面都是通過編寫xml代碼來實現的。

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表示讓當前控件的大小剛好可以包住里面的內容,也就是控件內容決定當前控件的大小。當然你也可以指定控件一個特定值的寬高的大小。(但是這樣可能會出現屏幕的適配問題)

如需修改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,這是由于系統會對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;

}

}

});

}

}

如果不喜歡使用匿名類的方式來注冊監聽器,也可以使用實現接口的方式來進行注冊

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開頭的目錄下

創建一個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一般都是用于提示一些非常重要的內容或者警告信息。

我們現在可以在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(貌似現在已經取消使用了)

與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)

布局的內部除了放置控件外,還可以放置布局,通過多層布局的嵌套,我們就能夠完成一些比較復雜的界面實現。

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使用比例的方式來指定控件的大?。ㄔ谑謾C屏幕的適配性方便非常重要)

案例:

<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是比較標準的寫法。

比重就是總數加起來,然后再根據占的比重來決定寬度,現在很明顯是EditText和Button平分寬度。

這時候如果Button的layout_width改為wrap_content,然后EditText保持1的比比重不變,那么Button就會保持固定大小,然后EditText占據剩余的寬度空間。

3.3.2 相對布局

相對布局RelativeLayout,相對隨意,它可以通過相對定位的方式讓控件出現在布局的任何位置。因此,屬性非常多,不過這些屬性都是有規律可循。

如案例: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=“left”

Android:layout_gravity=“right”

由于FrameLayout的定位方式的缺失,導致它的使用場景比較少。

3.3.4 百分比布局

前面3種布局都是從android 1.0版本就開始支持了,一直沿用到現在。這種布局中,我們可以不再使用wrap_content、match_parent等方式來指定控件的大小,而是允許直接指定控件在布局中所占的百分比。

百分比布局只為FrameLayout和RelativeLayout進行了功能拓展,提供了PercentFrameLayout和PercentRelativeLayout這兩種全新的布局。

怎樣做到新增的百分比布局在所有的android版本中都能使用呢。為此,android團隊將百分比布局定義在support庫當中。我們只需要在項目中build.gradle中添加百分比布局庫的以來,就能保證百分比布局在android所有系統版本上的兼容性了。

修改app/build.gradle文件中

Dependencies {

implementation ‘com.android.support:percent:28.0.0’

}

如果發現28.0.0版本不夠新,系統會提示你修改。修改完之后還需要重新build一下。(點擊Sync Now)

現在使用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 創建自定義控件

所有控件都是繼承自View,所有的布局都是直接或者間接繼承自ViewGroup的。

創建一個新的應用UICustomViews

單獨創建一個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中將系統自帶的標題欄隱藏掉

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 創建自定義控件

新建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控件就會調用這個構造函數,然后在構造函數中需要對標題欄布局進行動態加載,這就要借助LayoutInflater來實現。通過LayoutInflater的from()方法可以構建出一個ayoutInflater對象,然后調用inflate()方法就可以動態加載一個布局文件,inflate()方法兩個參數,第一個是加載的布局文件的id,這里傳入R.layout.title,第二個參數是給加載好的布局再添加一個父布局,這里我們想要指定為TitleLayout,于是直接傳入this。

現在這個自定義控件創建好了,然后我們需要在布局文件中添加這個自定義控件,修改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”,”Orange”,”watermelon”,”Pear”,”Grape”,”Pineapple”,”Strawberry”,”Cherry”,”Mango”,

“Apple”,”Banana”,”Orange”,”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);

}

}

我們需要通過借助適配器來把數據傳遞給ListView,Android中提供了很多適配器的實現類,其中我認為最好用的就是ArrayAdapter。它可以通過泛型來指定要適配的數據類型,然后在構造函數中把要適配的數據傳入。ArrayAdater有多個構造函數的重載,你應該根據實際情況選擇最合適的一種。上面在ArrayAdapter的構造函數中依次穿日當前上下文、ListView子項布局的id,以及要適配的數據。注意,我們使用了android.R.layout.simple_list_item_1作為子項布局的id,這是一個Android內置的布局文件,里面只有一個TextView,可用于簡單的顯示一段文本。

3.5.2 定制ListView的界面

只顯示一段文本的LIstView太單調了,現在我們來對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;

}

}

然后我們為ListVie的子項指定一個我們自定義的布局,在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在垂直方向上居中顯示。

接下來我們創建一個自定義的適配器,這個適配器繼承自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重寫了父類的一組構造函數,用于將上下文,ListView子項布局的id和數據傳遞過去。另外又重寫了getVIew()方法,這個方法在每一個子項被滾動到屏幕內的時候會被調動。在getView()方法中,首先通過getItem()方法得到當前項的Fruit實例,然后使用LayoutInflater來為這個子項加載我們傳入的布局。

這里的Layouinflater的inflater()方法接收3個參數,前面兩個參數我們都知道是什么意思了,第三個參數指定成flase,表示只讓我們在父布局中聲明的layout屬性生效,但不為這個View添加父布局,因為一旦View有了父布局之后,它就不能再天機到ListView中了。(現在不太明白沒關系,這其實是ListView中的標準寫法)

最后我們修改MainActivity中的代碼,

public class MainActivity extends AppCompatActivity {

private List<Fruit> fruitList = new ArrayList<>(); // 水果數據

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState) ;

setContentView(R.layout.activity_main);

initFruits(); // 初始化水果數據

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的參數,這個參數用于將之前加載好的布局進行緩存,以便之后可以進行重用。修改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;

}

}

雖然現在已經不會重復加載布局,但是每次在getView()的時候,還是會調用View的findViewById()方法來獲取一次控件的實例。繼續優化,使用ViewHolder來使得不會重復創建。

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雖然很好用,擴展性不好,只能實現縱向的滾動效果,而且如果我們不使用一些技巧來提升它的運行效率,那么LIstView的性能就會非常差。

RecyclerView是增強版的ListView,可以輕松實現ListView的效果,還優化了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并不是在系統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實現橫向滾動和瀑布流布局

首先對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>

現在修改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中制定了一套殼拓展的布局排列接口,子類只要按照接口的規范來實現,就能制定出各種不同排列方式的布局了。

處理LinearLayoutManger,RecyclerView還給我們提供了GridLayoutManger和StaggeredGridLayoutManager這兩種內置的布局排列方式。GridLayoutManaget可以用于實現網格布局,StaggeredGridLayoutManager可以用于實現瀑布流布局。

現在我們來實現更加炫酷的瀑布流布局(相當于iOS中的UICollectionVeiw)

繼續修改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,因為瀑布流布局的寬度應該是根據布局的列數來自動適配的。而不是一個固定的值。另外我們使用了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);

// 構造函數接收兩個參數,3表示布局分為3列,第二個參數用于指定布局的排列方式。

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();

// 創建一個1-20的隨機數,

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()這樣的注冊監聽器方法。而是需要我們自己給子項具體的view去注冊點擊事件,相對于ListView來說,實現起來要復雜一點。

ListView的點擊事件上的處理并不人性化,setOnItemClickListener()方法注冊的是子項的點擊事件,但如果我們想點擊的是子項里面具體的某一個按鈕呢,ListView也能實現,但是實現起來就相對比較麻煩了。為此,RecyclerView干脆直接摒棄了子項點擊事件的監聽器,所有的點擊事件都由具體的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的強大之處在于,它可以輕松實現子項中任意控件或布局的點擊事件。

3.7 編寫界面的最佳實踐。

現在我們結合之前所有的知識,來做衣蛾較為復雜且沒管的聊天界面。創建一個UIBestPractice項目。

具體代碼看Android Studio里面的代碼

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內容