第一行代码学习笔记第三章——UI开发的点点滴滴
知識點目錄
- 3.1 如何編寫程序界面
- 3.2 常用控件的使用方法
* 3.2.1 TextView
* 3.2.2 Button
* 3.2.3 EditText
* 3.2.4 ImageView
* 3.2.5 ProgressBar
* 3.2.6 AlertDialog
* 3.2.7 ProgressDialog
- 3.3 詳解4中基本布局
* 3.3.1 線性布局
* 3.3.2 相對布局
* 3.3.3 幀布局
* 3.3.4 百分比布局
- 3.4 創建自定義控件
* 3.4.1 引入布局
* 3.4.2 創建自定義控件
- 3.5 ListView
* 3.5.1 ListView的簡單用法
* 3.5.2 定制ListView的界面
* 3.5.3 提升ListView的運行效率
* 3.5.4 ListView的點擊事件
- 3.6 RecyclerView
* 3.6.1 RecyclerVeiw的基本用法
* 3.6.2 實現橫向滾動和瀑布流布局
* 3.6.3 RecyclerView的點擊事件
- 3.7 編寫界面的最佳實踐
* 3.7.1 制作Nine-Patch圖片
* 3.7.2 編寫精美的聊天界面
知識點回顧
3.1 如何編寫程序界面
Android開發中編寫界面的方法主要有如下兩種:
-
可視化編輯器
優點:允許拖放控件來編寫布局,同時可以在視圖上修改控件的屬性
缺點:不利于了解界面背后的原理,屏幕適配性不好
-
XML代碼
這是用的最多的方式。不僅能夠實現高度復雜的控件,還能分析和修改當前現有界面。
3.2 常用控件的使用方法
Android中提供了大量的UI控件,下面我們學習幾種比較常用的控件。
3.2.1 TextView
主要作用:
在界面上顯示一段文本信息。
使用方法:
<TextViewandroid:id="@+id/text_view"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center"android:textSize="24sp"android:textColor="#00ff00"android:text="This is TextView" />屬性說明:
-
android:id 給當前控件定義一個唯一標識符。
-
android:layout_width和android:layout_height指定控件的寬度和高度,Android中所有的控件都有這兩個屬性,值有match_parent、fill_parent和wrap_content。其中:match_parent和fill_parent意義相同,表示讓當前控件的大小和父布局的大小一樣;wrap_content表示讓當前控件的大小能夠剛好包含住里面的內容。
-
android:gravity 指定文字的對齊方式,可選值有top、bottom、left、right、center等??梢杂谩皘”來同時指定多個值。
-
android:textSize 指定文字的大小。Android中字體大小使用sp作為單位。
-
android:textColor 指定文字顏色。
-
android:text 指定TextView中顯示的文本內容。
這里簡單的解釋了TextView中的幾個屬性,其它屬性等用到的時候自行查閱。
3.2.2 Button
主要作用:
顯示一個按鈕,與用戶進行交互。
Button可配置的屬性和TextView差不多。
使用方法:
<Buttonandroid:id="@+id/button"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Button"android:textAllCaps="false"/>屬性說明:
其它屬性跟上面說的TextView一樣,這里說一個特殊的。
- android:textAllCaps 如果不加這個屬性,那么上面設置的文字Button最終顯示的卻是“BUTTON”,這是因為系統會對Button中的所有英文字母自動進行大寫轉換,如果不想要這個效果,可以將android:textAllCaps設置為false。
Button注冊監聽器
-
方式一(匿名類):
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button button = (Button) findViewById(R.id.button);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 在此處添加邏輯}}); }} -
方式二(實現接口):
public class MainActivity extends AppCompatActivity implements View.OnClickListener {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button button = (Button) findViewById(R.id.button);button.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.button:// 在此處添加邏輯break;default:break;}}}
3.2.3 EditText
主要作用:
用于和用戶進行交互,用戶可以在控件里面輸入和編輯內容,并可以在程序中對這些內容進行處理。
使用方法:
<EditTextandroid:id="@+id/edit_text"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="Type something here"android:maxLines="2"/>屬性說明:
-
android:hint 指定一段提示性文本,用戶輸入后,文本自動消失。
-
android:maxLines 指定EditText的最大行數,當輸入的內容超過行數后,文本就會向上滾動。
獲取輸入框文本:
EditText editText = (EditText) findViewById(R.id.edit_text); String inputText = editText.getText().toString();3.2.4 ImageView
主要作用:
用于在界面上展示圖片。圖片根據分辨率的不同放在不同的drawable或mipmap目錄下。
使用方法:
<ImageViewandroid:id="@+id/image_view"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@mipmap/ic_launcher"/>屬性說明:
- android:src 給ImageView指定一張圖片。
代碼中指定圖片:
ImageView imageView = (ImageView) findViewById(R.id.image_view); imageView.setImageResource(R.mipmap.ic_launcher_round);3.2.5 ProgressBar
主要作用:
在界面上顯示一個進度條。
使用方法:
<ProgressBarandroid:id="@+id/progress_bar"android:layout_width="match_parent"android:layout_height="wrap_content"android:visibility="visible"style="?android:attr/progressBarStyleHorizontal"android:max="100"/>屬性說明:
-
android:visibility 控制控件是否可見。 visible表示控件可見,默認值。invisible表示控件不可見,但仍占據原來的位置,相當于控件透明。gone表示控件不可見,且不占據原來的位置。
-
style 指定控件的形狀。
-
android:max 給進度條設置一個最大值。
代碼中控制控件是否可見
ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar); if (progressBar.getVisibility() == View.GONE) {progressBar.setVisibility(View.VISIBLE); } else {progressBar.setVisibility(View.GONE); }3.2.6 AlertDialog
主要作用:
用于提示一些非常重要的內容或者警告信息。比如為了防止用戶誤刪重要內容,在刪除前彈出一個確認對話框。
使用方法:
AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);dialog.setTitle("This is Dialog");dialog.setMessage("Something improtant");dialog.setCancelable(false);dialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {}});dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {}});dialog.show();使用說明:
-
AlertDialog.Builder創建一個AlertDialog實例
-
setTitle 設置標題
-
setMessage 設置內容
-
setCancelable 是否用Back鍵關閉對話框
-
setPositiveButton() 設置確認按鈕的點擊事件
-
setNegativeButton() 設置取消按鈕的點擊事件
-
show() 將dialog顯示出來
3.2.7 ProgressDialog
主要作用:
跟AlertDialog類似,都能夠屏蔽掉其他控件的交互能力。只是ProgressDialog會在對話框中顯示一個進度條,一般用于表示當前操作比較耗時,讓用戶耐心地等待。
使用方法:
ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);progressDialog.setTitle("This is ProgressDialog");progressDialog.setMessage("Loding...");progressDialog.setCancelable(true);progressDialog.show();使用說明:
-
new ProgressDialog 創建一個ProgressDialog對象
-
setTitle 設置標題
-
setMessage 設置內容
-
setCancelable 是否用Back鍵關閉對話框
-
show() 將dialog顯示出來
備注:
如果在setCancelable()中傳入了false,表示ProgressDialog不能通過Back鍵取消掉,這時需要在代碼中做好控制,當數據加載完成后必須調用ProgressDialog的dismiss()方法來關閉對話框,否則ProgressDialog將會一直存在。
3.3 詳解4中基本布局
布局是一種可以放置多個控件或者布局的容器,可以按照一定的規律調整內部控件或布局的位置,從而來寫出精美的界面。
3.3.1 線性布局
LinearLayout稱作線性布局,該布局會將它所包含的控件在線性方向(水平或垂直方向)上依次排列。
基本使用:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="horizontal"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:id="@+id/button1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="top"android:text="Button 1"/><Buttonandroid:id="@+id/button2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:text="Button 2"/><Buttonandroid:id="@+id/button3"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="bottom"android:text="Button 3"/><EditTextandroid:id="@+id/input_message"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:hint="Type something"/><Buttonandroid:id="@+id/send"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="send"/></LinearLayout>屬性說明:
-
android:orientation 指定排列方向,可以是vertical或horizontal。默認的排列方向是horizontal
-
android:layout_weight 使用比例的方式來指定控件的大小
備注:
-
如果LinearLayout的排列方向是horizontal,內部的控件寬度就不能指定為match_parent,因為這樣的話,單獨的一個控件就會將整個水平方向占滿,其他的控件就沒有可放的位置。同理,如果LinearLayout的排列方向是vertical,內部控件的高度就不能指定為match_parent。
-
android:layout_gravity用于指定控件在布局中的對齊方式,而android:gravity用于指定文字在控件中的對齊方式。
-
LinearLayout的排列方式是horizontal時,只有在垂直方向上的對齊方式才會生效,因為此時水平方向上的長度是不固定的,每添加一個控件,水平方向上的長度都會改變,因此無法確定該方向上的對齊方式。同理,當LinearLayout的排列方向是vertical時,只有水平方向上的對齊方式才會生效。
3.3.2 相對布局
RelativeLayout稱作相對布局。通過相對定位的方式讓控件出現在布局的任何位置。
基本使用:
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid: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"/><Buttonandroid: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"/><Buttonandroid:id="@+id/button3"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="Button 3"/><Buttonandroid: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"/><Buttonandroid: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"/><Buttonandroid:id="@+id/button6"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_above="@id/button3"android:layout_toLeftOf="@id/button3"android:text="Button 6"/><Buttonandroid:id="@+id/button7"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_above="@id/button3"android:layout_toRightOf="@id/button3"android:text="Button 7"/><Buttonandroid:id="@+id/button8"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/button3"android:layout_toLeftOf="@id/button3"android:text="Button 8"/><Buttonandroid:id="@+id/button9"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/button3"android:layout_toRightOf="@id/button3"android:text="Button 9"/></RelativeLayout>效果圖:
屬性說明:
-
android:layout_alignParentLeft 與父布局的左邊對齊
-
android:layout_alignParentRight 與父布局的右邊對齊
-
android:layout_alignParentTop 與父布局的上邊對齊
-
android:layout_alignParentBottom 與父布局的下邊對齊
-
android:layout_centerInParent 位于父布局中心
上面的屬性主要用在控件相對于父布局進行定位的屬性。但控件也可以相對于其他控件進行定位!
-
android:layout_above 位于相對控件的上方
-
android:layout_below 位于相對控件的下方
-
android:layout_toLeftOf 位于相對控件的左側
-
android:layout_toRightOf 位于相對控件的右側
**備注:**當一個控件去引用另一個控件id的時候,該控件一定要定義在引用控件的后面,不然會出現找不到id的情況。
3.3.3 幀布局
FrameLayout稱作幀布局。它里面所有的控件都會默認擺放在布局的左上角。
基本使用:
<FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:id="@+id/text_view"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="left"android:text="This is TextView"/><ImageViewandroid:id="@+id/image_view"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="right"android:src="@mipmap/ic_launcher"/></FrameLayout>屬性說明:
- android:layout_gravity 指定控件在布局中的對齊方式。
3.3.4 百分比布局
在前面介紹的LinearLayout、RelativeLayout和FrameLayout三種布局,只有LinearLayout支持使用layout_weight屬性來實現按比例指定控件大小的功能。為此,Android引入了百分比布局。我們不再需要使用wrap_content、match_parent等方式來指定控件的大小,而是直接指定控件在布局中所占的百分比,這樣就可以實現任意比例分割布局的效果。
由于LinearLayout本身支持按比例指定控件的大小,因此百分比布局只為RelativeLayout和FrameLayout進行了功能擴展,提供了PercentFrameLayout和PercentRelativeLayout這兩個布局。
Android開發人員將百分比布局定義在了support庫中,因此我們需要在項目的build.gradle中添加百分比布局庫的依賴,就能保證百分比布局在Android所以系統版本上的兼容性。
打開app/build.gradle文件,在dependencies閉包中添加如下內容:
dependencies {implementation 'com.android.support:percent:25.3.1' }基本使用:
<android.support.percent.PercentFrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:id="@+id/button1"android:text="Button 1"android:layout_gravity="left|top"app:layout_widthPercent="50%"app:layout_heightPercent="50%"/><Buttonandroid:id="@+id/button2"android:text="Button 2"android:layout_gravity="right|top"app:layout_widthPercent="50%"app:layout_heightPercent="50%"/><Buttonandroid:id="@+id/button3"android:text="Button 3"android:layout_gravity="left|bottom"app:layout_widthPercent="50%"app:layout_heightPercent="50%"/><Buttonandroid:id="@+id/button4"android:text="Button 4"android:layout_gravity="right|bottom"app:layout_widthPercent="50%"app:layout_heightPercent="50%"/></android.support.percent.PercentFrameLayout>效果圖:
屬性說明:
-
android.support.percent.PercentFrameLayout 由于百分比布局并不是內置在系統SDK當中,所以需要把完整的包路徑寫出來
-
xmlns:app 定義一個app的命名空間,這樣才能使用百分比布局的自定義屬性
-
app:layout_widthPercent 指定控件的寬度在布局中所占的百分比
-
app:layout_heightPercent 指定控件的高度在布局中所占的百分比
-
PercentFrameLayout繼承了FrameLayout的特性,所以我們需要通過android:layout_gravity屬性來指定放置布局的位置。
PercentRelativeLayout的用法同PercentFrameLayout很類似。它繼承了RelativeLayout的所有特性??砂凑誔ercentFrameLayout自行嘗試。
3.4 創建自定義控件
前面學習了Android中的常用控件和布局,順便梳理下控件和布局之間的繼承關系:
從上面的圖中可得出如下結論:
-
所有控件都是直接或間接繼承自View
-
所有的布局都是直接或間接繼承自ViewGroup
-
View是Android中最基本的一種UI控件,它可以在屏幕上繪制一塊矩形區域,并能響應這塊區域的各種事件
-
ViewGroup是一種特殊的View,它可以包含很多子View和子ViewGroup,是一個用于放置控件和布局的容器
3.4.1 引入布局
在平時的項目開發中,很多活動可能都需要相同樣式的標題欄,如果在每個活動中都編寫一遍同樣的標題欄代碼,就會導致代碼的大量重復,這時候我們就可以用引入布局的方式來解決這個問題。
新建布局:
新建一個title.xml,代碼如下所示:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/title_bg"><Buttonandroid:id="@+id/title_back"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_margin="5dp"android:background="@drawable/back_bg"android:text="Back"android:textColor="#fff"/><TextViewandroid:id="@+id/title_text"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_weight="1"android:gravity="center"android:text="Title Text"android:textColor="#fff"android:textSize="24sp"/><Buttonandroid:id="@+id/title_edit"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_margin="5dp"android:background="@drawable/edit_bg"android:text="Edit"android:textColor="#fff"/></LinearLayout>屬性說明:
-
android:background 用于給控件或布局指定一個背景
-
android:layout_margin 指定控件在上下左右方向上偏移的距離,單位為dp
使用標題欄:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><include layout="@layout/title"/></LinearLayout>通過使用include語句將標題欄布局引入進來。
隱藏系統自帶的標題欄
在顯示我們通過引入布局寫的標題欄前,我們需要隱藏掉系統自帶的標題欄:
ActionBar actionBar = getSupportActionBar(); if (actionBar != null) {actionBar.hide(); }通過調用getSupportActionBar()獲得ActionBar的實例,然后調用ActionBar的hide()方法將標題欄隱藏。
效果圖:
3.4.2 創建自定義控件
引入布局很好的解決了重復編寫布局代碼的問題,但是如果布局中有一些控件要求能夠響應事件,那么我們就需要在每個活動中單獨編寫一次事件注冊的代碼。比如標題欄中的返回按鈕,其實不管在哪個活動中,這個按鈕的功能都是銷毀當前活動。如果每一個活動中都需要重新注冊一遍返回按鈕的點擊事件,這樣就會增加很多重復代碼,此時我們就應使用自定義控件的方式來解決。
自定義控件
public class TitleLayout extends LinearLayout {public TitleLayout(Context context, @Nullable 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() {@Overridepublic void onClick(View v) {((Activity) getContext()).finish();}});titleEdit.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(getContext(), "You click Edit button", Toast.LENGTH_SHORT).show();}});} }-
重寫了LinearLayout帶有兩個參數的構造函數,在布局中引入TitleLayout控件就會調用這個構造函數
-
使用LayoutInflater對標題欄布局進行動態加載,LayoutInflater的from()方法構建出一個LayoutInflater對象,然后調用inflate()方法動態加載一個布局。
-
inflate()方法第一個參數是要加載布局文件的id,第二個參數是給加載好的布局添加一個父布局。
-
通過findViewById()找到布局文件中的控件,分別為各個按鈕注冊點擊事件。
在布局中使用自定義控件
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><com.example.uicustomviews.TitleLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"/> </LinearLayout>備注:
在使用自定義控件的時候,我們需要指定控件的完整類名。
效果圖:
3.5 ListView
ListView允許用戶通過手指上下滑動的方式將屏幕外的數據滾動到屏幕內,同時屏幕上原有的數據則會滾動出屏幕。
3.5.1 ListView的簡單用法
在activity_main.xml中的使用:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><ListViewandroid: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",};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ListView listView = (ListView) findViewById(R.id.list_view);ArrayAdapter<String> adapter = new ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_list_item_1,data);listView.setAdapter(adapter);} }-
data 是ListView中要顯示的數據,可以自定義,也可以從后臺獲取
-
ArrayAdapter 數據無法直接傳給ListView,需要借助適配器來完成。而ArrayAdapter是Android中提供的適配器實現類,可以通過泛型來指定要適配的數據類型。
-
ArrayAdapter 構造函數參數一:當前上下文;參數二:ListView子項布局的id;參數三:要適配的數據。
-
android.R.layout.simple_list_item_1 是一個Android內置的布局,里面只有一個TextView,可用于簡單地顯示一段文本。
-
listView.setAdapter() 通過該方法將構建好的適配器對象傳遞進去,這樣ListView和數據之間就建立了關聯。
效果圖:
3.5.2 定制ListView的界面
ListView的子項布局正常情況下不止一段文本,下面我們就在上面每個子項布局的水果旁邊加上一個圖樣。
定義實體類
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;} }其中:name表示水果的名字,imageId表示水果對應圖片的資源id。
自定義ListView子項布局
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/fruit_image"android:layout_width="wrap_content"android:layout_height="wrap_content"/><TextViewandroid:id="@+id/fruit_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:layout_marginLeft="10dp"/></LinearLayout>自定義適配器
public class FruitAdapter extends ArrayAdapter<Fruit> {private int resourceId;public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {super(context, resource, objects);resourceId = resource;}@NonNull@Overridepublic View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {Fruit fruit = getItem(position);View view = LayoutInflater.from(getContext()).inflate(resourceId, parent,false);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;} }-
FruitAdapter 重寫父類個構造函數,將上下文、ListView子項布局id和數據傳遞進來
-
重寫getView()方法。每個子項滾動到屏幕內時都會調用這個方法。
-
getItem()方法得到當前項的Fruit實例
-
LayoutInflater 為子項加載我們傳入的布局
-
inflate()方法的參數三要傳入false,表示只讓我們在父布局中聲明的layout生效,但不會為這個View添加父布局,因為View一旦有了父布局,就不能將它添加到ListView中
-
調用View的findViewById()方法分別獲取到ImageView和TextView的實例,然后分別調用setImageResource()和setText()來設置顯示圖片和文字
在MainActivity中的代碼:
public class MainActivity extends AppCompatActivity {private List<Fruit> mFruitList = new ArrayList<>();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initFruits();ListView listView = (ListView) findViewById(R.id.list_view);FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, mFruitList);listView.setAdapter(adapter);}private void initFruits() {for (int i = 0; i < 2; i++) {Fruit apple = new Fruit("Apple", R.drawable.apple_pic);mFruitList.add(apple);Fruit banana = new Fruit("Banana", R.drawable.banana_pic);mFruitList.add(banana);Fruit orange = new Fruit("Orange", R.drawable.orange_pic);mFruitList.add(orange);Fruit watermelon = new Fruit("Watermelon", R.drawable.watermelon_pic);mFruitList.add(watermelon);Fruit pear = new Fruit("Pear", R.drawable.pear_pic);mFruitList.add(pear);Fruit grape = new Fruit("Grape", R.drawable.grape_pic);mFruitList.add(grape);Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic);mFruitList.add(pineapple);Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic);mFruitList.add(strawberry);Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic);mFruitList.add(cherry);Fruit mango = new Fruit("Mango", R.drawable.mango_pic);mFruitList.add(mango);}} }效果圖
3.5.3 提升ListView的運行效率
上面實例中的ListView運行效率很低,主要原因有如下兩點:
-
FruitAdapter的getView方法中,每次都將布局重新加載一遍
-
FruitAdapter的getView方法中,每次都會調用View的findViewById()方法去獲取控件的實例
針對上面的兩種情況,我們可以使用convertView和ViewHolder去優化:
public class FruitAdapter extends ArrayAdapter<Fruit> {private int resourceId;public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {super(context, resource, objects);resourceId = resource;}@NonNull@Overridepublic View getView(int position, @Nullable View convertView, @NonNull 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.fruitImage.setImageResource(fruit.getImageId());viewHolder.fruitName.setText(fruit.getName());return view;}class ViewHolder {ImageView fruitImage;TextView fruitName;} }-
在getView()方法中,判斷convertView是否為null,如果為null,則使用LayoutInflater去加載布局;如果不為null,則直接對convertView重用
-
新建一個內部類ViewHolder,然后分別使用setTag()和getTag()方法去存儲和取出
3.5.4 ListView的點擊事件
使用setOnItemClickListener()方法為ListView注冊一個監聽器:
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initFruits();ListView listView = (ListView) findViewById(R.id.list_view);FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, mFruitList);listView.setAdapter(adapter);//注冊監聽器listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {Fruit fruit = mFruitList.get(position);Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show();}}); }當用戶點擊了ListView中的任何一個子項時,就會回調onItemClick()方法,這個方法中我們可以通過position參數來判斷出用戶點擊的是哪一個子項。
3.6 RecyclerView
ListView只能實現數據縱向滾動效果,無法實現橫向滾動。因此Android提供了一個增強版的ListView——RecyclerView。
3.6.1 RecyclerVeiw的基本用法
1. 添加依賴
RecyclerVeiw屬于新增控件,Android團隊將其定義在了support庫中,因此我們需要在項目的build.gradle中添加相應的依賴庫。
dependencies {implementation 'com.android.support:recyclerview-v7:24.2.1' }2. 布局中使用
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><android.support.v7.widget.RecyclerViewandroid:id="@+id/recycler_view"android:layout_width="match_parent"android:layout_height="match_parent"/></LinearLayout>由于RecyclerView并不是內置在系統SDK當中的,所以需要把完整的包路徑寫出來。
3. 新建適配器
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {private List<Fruit> mFruitList;public FruitAdapter(List<Fruit> fruitList) {mFruitList = fruitList;}//創建ViewHolder實例@Overridepublic 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;}//對RecyclerView子項的數據進行賦值@Overridepublic void onBindViewHolder(ViewHolder holder, int position) {Fruit fruit = mFruitList.get(position);holder.fruitImage.setImageResource(fruit.getImageId());holder.fruitName.setText(fruit.getName());}//RecyclerView有多少子項@Overridepublic int getItemCount() {return mFruitList.size();}static class ViewHolder extends RecyclerView.ViewHolder {ImageView fruitImage;TextView fruitName;public ViewHolder(View itemView) {super(itemView);fruitImage = ((ImageView) itemView.findViewById(R.id.fruit_image));fruitName = ((TextView) itemView.findViewById(R.id.fruit_name));}} }4. 真正使用RecyclerView
public class MainActivity extends AppCompatActivity {private List<Fruit> mFruitList = new ArrayList<>();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initFruits();RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);LinearLayoutManager layoutManager = new LinearLayoutManager(this);recyclerView.setLayoutManager(layoutManager);FruitAdapter adapter = new FruitAdapter(mFruitList);recyclerView.setAdapter(adapter);}private void initFruits() {for (int i = 0; i < 2; i++) {Fruit apple = new Fruit("Apple", R.drawable.apple_pic);mFruitList.add(apple);Fruit banana = new Fruit("Banana", R.drawable.banana_pic);mFruitList.add(banana);Fruit orange = new Fruit("Orange", R.drawable.orange_pic);mFruitList.add(orange);Fruit watermelon = new Fruit("Watermelon", R.drawable.watermelon_pic);mFruitList.add(watermelon);Fruit pear = new Fruit("Pear", R.drawable.pear_pic);mFruitList.add(pear);Fruit grape = new Fruit("Grape", R.drawable.grape_pic);mFruitList.add(grape);Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic);mFruitList.add(pineapple);Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic);mFruitList.add(strawberry);Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic);mFruitList.add(cherry);Fruit mango = new Fruit("Mango", R.drawable.mango_pic);mFruitList.add(mango);}} }LayoutManager用于指定RecyclerView的布局方式,這里使用的LinearLayoutManager是線性布局的意思,可以實現和ListView類似的效果。
5. RecyclerView效果圖
3.6.2 實現橫向滾動和瀑布流布局
橫向滾動
1. 修改fruit_item.xml文件
因為要橫向滾動,所以需要對上面的fruit_item.xml文件進行修改:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="100dp"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/fruit_image"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"/><TextViewandroid:id="@+id/fruit_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_marginTop="10dp"/></LinearLayout>2. 設置布局橫向滾動
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initFruits();RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);LinearLayoutManager layoutManager = new LinearLayoutManager(this);//設置RecyclerView的方向layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);recyclerView.setLayoutManager(layoutManager);FruitAdapter adapter = new FruitAdapter(mFruitList);recyclerView.setAdapter(adapter);}RecyclerView的布局排列是由LayoutManager去控制,LayoutManager中制定了一套可擴展的布局排列接口,子類只要按照接口的規范來實現,就能定制出各種不同排列方式的布局。
3. 效果圖
瀑布流布局
除了LinearLayoutManager之外,RecyclerView還提供了GridLayoutManager和StaggerdGridLayoutManager這兩種內置的布局排列方式。其中,GridLayoutManager可以用于實現網格布局,StaggerdGridLayoutManager可以用于實現瀑布流布局。
1. 修改fruit_item.xml文件
因為要實現瀑布流效果,所以需要對上面的fruit_item.xml文件進行修改:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="5dp"><ImageViewandroid:id="@+id/fruit_image"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"/><TextViewandroid:id="@+id/fruit_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="left"android:layout_marginTop="10dp"/></LinearLayout>2. 使用StaggerdGridLayoutManager
public class MainActivity extends AppCompatActivity {private List<Fruit> mFruitList = new ArrayList<>();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initFruits();RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);recyclerView.setLayoutManager(layoutManager);FruitAdapter adapter = new FruitAdapter(mFruitList);recyclerView.setAdapter(adapter);}private void initFruits() {for (int i = 0; i < 2; i++) {Fruit apple = new Fruit(getRandomLengthName("Apple"), R.drawable.apple_pic);mFruitList.add(apple);Fruit banana = new Fruit(getRandomLengthName("Banana"), R.drawable.banana_pic);mFruitList.add(banana);Fruit orange = new Fruit(getRandomLengthName("Orange"), R.drawable.orange_pic);mFruitList.add(orange);Fruit watermelon = new Fruit(getRandomLengthName("Watermelon"), R.drawable.watermelon_pic);mFruitList.add(watermelon);Fruit pear = new Fruit(getRandomLengthName("Pear"), R.drawable.pear_pic);mFruitList.add(pear);Fruit grape = new Fruit(getRandomLengthName("Grape"), R.drawable.grape_pic);mFruitList.add(grape);Fruit pineapple = new Fruit(getRandomLengthName("Pineapple"), R.drawable.pineapple_pic);mFruitList.add(pineapple);Fruit strawberry = new Fruit(getRandomLengthName("Strawberry"), R.drawable.strawberry_pic);mFruitList.add(strawberry);Fruit cherry = new Fruit(getRandomLengthName("Cherry"), R.drawable.cherry_pic);mFruitList.add(cherry);Fruit mango = new Fruit(getRandomLengthName("Mango"), R.drawable.mango_pic);mFruitList.add(mango);}}private String getRandomLengthName(String name) {Random random = new Random();int length = random.nextInt(20) + 1;StringBuilder builder = new StringBuilder();for (int i = 0; i < length; i++) {builder.append(name);}return builder.toString();} }StaggeredGridLayoutManager的構造函數接受兩個參數,參數一:用于指定布局的列數,傳入3表示會把布局分為3列;參數二用于指定布局的排列方向。
3. 效果圖
3.6.3 RecyclerView的點擊事件
RecyclerView并沒有提供類似于setOnItemClickListener()這樣的注冊監聽器方法,而是需要我們自己給子項具體的View去注冊點擊事件。
其實ListView的setOnItemClickListener()方法注冊的是子項的點擊事件,如果想點擊子項里面具體的某一個按鈕,ListView實現起來就有點復雜。為此,RecyclerView干脆直接摒棄了子項點擊事件的監聽器,所有的點擊事件都由具體的View去注冊。
注冊監聽
RecyclerView的點擊事件是在Adapter中實現的,修改FruitAdapter文件:
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {private List<Fruit> mFruitList;public FruitAdapter(List<Fruit> fruitList) {mFruitList = fruitList;}//創建ViewHolder實例@Overridepublic 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.fruitView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {int position = holder.getAdapterPosition();Fruit fruit = mFruitList.get(position);Toast.makeText(v.getContext(), "you clicked view " + fruit.getName(), Toast.LENGTH_SHORT).show();}});//給子項中的ImageView注冊點擊事件holder.fruitImage.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {int position = holder.getAdapterPosition();Fruit fruit = mFruitList.get(position);Toast.makeText(v.getContext(), "you clicked image " + fruit.getName(), Toast.LENGTH_SHORT).show();}});return holder;}//對RecyclerView子項的數據進行賦值@Overridepublic void onBindViewHolder(ViewHolder holder, int position) {Fruit fruit = mFruitList.get(position);holder.fruitImage.setImageResource(fruit.getImageId());holder.fruitName.setText(fruit.getName());}//RecyclerView有多少子項@Overridepublic int getItemCount() {return mFruitList.size();}static class ViewHolder extends RecyclerView.ViewHolder {ImageView fruitImage;TextView fruitName;View fruitView; //子項最外層布局實例public ViewHolder(View itemView) {super(itemView);fruitView = itemView;fruitImage = ((ImageView) itemView.findViewById(R.id.fruit_image));fruitName = ((TextView) itemView.findViewById(R.id.fruit_name));}} }實現步驟:
-
在ViewHolder中添加fruitView變量保存子項最外層布局的實例
-
在onCreateViewHolder中注冊相關控件的點擊事件
效果圖
3.7 編寫界面的最佳實踐
3.7.1 制作Nine-Patch圖片
在Android sdk目錄下有一個tools文件夾,在這個文件夾中找到draw9patch.bat文件,我們使用他來制作Nine-Patch
要打開這個文件,必須先將JDK的bin目錄配置到環境變量中。例如使用的是AndroidStudio內置的sdk,要配置的路徑就是AndroidStudio安裝目錄/jre/bin
雙擊打開draw9patch.bat文件,在導航欄點擊File——>Open 9-patch將需要制作的圖片加載進來,如下圖所示:
可以在圖片的四個邊框繪制一個個的小黑點。上邊框和左邊框繪制的部分表示當圖片需要拉伸時就拉伸黑點標記的區域;下邊框和右邊框繪制的部分表示內容會被放置的區域。
3.7.2 編寫精美的聊天界面
略略略。。。
非常感謝您的耐心閱讀,希望我的文章對您有幫助。歡迎點評、轉發或分享給您的朋友或技術群。
總結
以上是生活随笔為你收集整理的第一行代码学习笔记第三章——UI开发的点点滴滴的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 生成和合入patch的两种方式
- 下一篇: 网络请求工具