第一行代碼讀書筆記 3 -- UI 開發(fā)

本篇文章主要介紹以下幾個知識點:

  • 百分比布局;
  • 引入布局,自定義控件朵纷;
  • RecyclerView 的用法;
  • 制作 Nine_Patch 圖片永脓;
  • 實戰(zhàn) 實現(xiàn)一個聊天界面袍辞。
圖片來源于網(wǎng)絡(luò)

3.1 百分比布局

百分比布局屬于新增布局,在這種布局中常摧,可以不再使用 wrap_content搅吁、match_parent 等方式來指定控件的大小威创,而是允許直接指定控件布局中所占的百分比,可以輕松實現(xiàn)平分布局甚至任意比例分割布局的效果谎懦。
??
百分比布局只為 FrameLayoutRelativeLayout 進行功能擴展肚豺,提供了 PercentFrameLayoutPercentRelativeLayout 這兩個全新的布局莉御。
??
用法:在項目的 build.gradle 中添加百分比布局庫的依賴:


dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:24.2.1'
    compile 'com.android.support:percent:24.2.1'
    testCompile 'junit:junit:4.12'
}

接下來修改 activity_percent.xml 中的布局代碼咬腋,如下:

<android.support.percent.PercentFrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_percent"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/button1"
        android:text="button1"
        android:layout_gravity="left|top"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>

    <Button
        android:id="@+id/button2"
        android:text="button2"
        android:layout_gravity="right|top"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>

    <Button
        android:id="@+id/button3"
        android:text="button3"
        android:layout_gravity="left|bottom"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>

    <Button
        android:id="@+id/button4"
        android:text="button4"
        android:layout_gravity="right|bottom"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>

</android.support.percent.PercentFrameLayout>

最外層使用了 PercentFrameLayout,由于百分比布局并不是內(nèi)置在系統(tǒng) SDK 當(dāng)中的歹嘹,所以需要把完整路徑寫下來享甸。然后定義一個 app 的命名空間截碴,方可使用百分比布局的自定義屬性。PercentFrameLayout 繼承了 FrameLayout 的特性蛉威。
??
上面定義了4個按鈕隐岛,使用 app:layout_widthPercentapp:layout_heightPercent 屬性將各按鈕的寬度瓷翻、高度指定為布局的50%聚凹,效果如圖:

PercentFrameLayout運行效果

可以看到,每一個按鈕的寬高都占據(jù)了布局的50%齐帚,輕松實現(xiàn)了4個按鈕平分屏幕的效果妒牙。

另外一個 PercentRelativeLayout 的用法類似,繼承了 RelativeLayout 中的所有屬性对妄,并可以使用app:layout_widthPercentapp:layout_heightPercent 來按百分比指定控件的寬高湘今。

3.2 創(chuàng)建自定義控件

我們所用的所有控件都是直接或間接繼承自 View 的,所用的所有布局都是直接或間接繼承自 ViewGroup 的剪菱。

ViewAndroid 中一種最基本的 UI 組件摩瞎,它可以在屏幕上繪制一塊矩形區(qū)域,并能響應(yīng)這塊區(qū)域的各種事件孝常,也就是說各種控件其實就是在 View 的基礎(chǔ)之上又添加了各自特有的功能旗们。

ViewGroup 則是一種特殊的 View,它可以包含很多的子 View子 ViewGroup构灸,是一個用于放置控件和布局的容器上渴。
??
如圖所示:

常用控件和布局的繼承結(jié)構(gòu)

3.2.1 引入布局

來實現(xiàn)個類似 iPhone 應(yīng)用的界面頂部的標(biāo)題欄, 標(biāo)題欄上有兩個按鈕可用于返回或其他操作(iPhone 沒有實體返回鍵)喜颁。
??
新建個布局 title.xml 如下:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/colorAccent" >

    <Button
        android:id="@+id/title_back"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_gravity="center"
        android:layout_margin="5dip"
        android:background="@drawable/bg_back"
        android:textColor="#fff" />

    <TextView
        android:id="@+id/title_text"
        android:layout_width="0dip"
        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" />

    <Button
        android:id="@+id/title_edit"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_gravity="center"
        android:layout_margin="5dip"
        android:background="@drawable/bg_message"
        android:textColor="#fff" />

</LinearLayout>

LinearLayout 中分別加入了兩個 Button 和一個 TextView稠氮。
??
如何在程序中使用這個標(biāo)題欄布局,修改 activity_custom_title.xml 中的代碼如下:

<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" >
    
    <include layout="@layout/title" />
    
</LinearLayout>

只需通過一行 include 語句將標(biāo)題欄布局引入進來即可半开。 最后別忘了在 CustomTitleActivity 中將系統(tǒng)自帶的標(biāo)題欄隱藏掉隔披,如下:

public class CustomTitleActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_custom_title);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null){
            actionBar.hide(); // 隱藏系統(tǒng)自帶的標(biāo)題欄
        }
    }
}

運行程序,效果如下:

引入標(biāo)題欄布局的效果

使用這種方式寂拆,不管有多少布局需要添加標(biāo)題欄奢米,只需一行 include 語句就可以了芥炭。

3.2.2 創(chuàng)建自定義控件

引入布局的技巧解決了重復(fù)編寫布局代碼的問題,但若布局中有一些控件要求能夠響應(yīng)事件恃慧,還需要在每個活動中為這些控件單獨編寫一次事件注冊的代碼园蝠。如標(biāo)題欄中的返回按鈕,其實不管是在哪一個活動中痢士,這個按鈕的功能都是相同的彪薛,即銷毀掉當(dāng)前活動。

而如果在每一個活動中都需要重新注冊一遍返回按鈕的點擊事件怠蹂,無疑又增加了很多重復(fù)代碼善延,此時最好是使用自定義控件的方式來解決。

新建 TitleLayout 繼承自 LinearLayout城侧,讓它成為自定義的標(biāo)題欄控件易遣,代碼如下:

/**
 * 自定義標(biāo)題欄
 * Created by KXwon on 2016/12/9.
 */
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 titleMessage = (Button) findViewById(R.id.title_message); 
        // 設(shè)置點擊事件
        titleBack.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) { 
                // 點擊返回按鈕銷毀當(dāng)前活動
                ((Activity) getContext()).finish();
            }
        });

        titleMessage.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getContext(), "You clicked Message button", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

現(xiàn)在自定義控件已經(jīng)創(chuàng)建好了,然后需要在布局文件中添加這個自定義控件嫌佑,修改 activity_custom_title.xml 中的代碼如下:

<LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent" >

   <com.wonderful.myfirstcode.custom_controls.TitleLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"/>

</LinearLayout>

重新運行程序豆茫,效果如圖所示:

自定義標(biāo)題欄效果

這樣每當(dāng)在一個布局中引入 TitleLayout,省去了很多編寫重復(fù)代碼的工作屋摇。

3.3 強大的滾動控件——RecyclerView

RecyclerView 可以說是一個增強版的 ListView揩魂,不僅可以輕松實現(xiàn) ListView 同樣的效果,還優(yōu)化了 ListView 中存在的各種不足炮温。

3.3.1 RecyclerView 的基本用法

使用 RecyclerView 這個控件火脉,首先需要在項目的 build.gradle 中添加相應(yīng)的依賴庫才行。

打開 app/build.gradle 文件柒啤,在 dependencies 閉包中添加如下內(nèi)容:

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'
}

然后修改 activity_recycler_view.xml 中的代碼:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_recycler_vew"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.wonderful.myfirstcode.custom_controls.recycler_view.RecyclerVewActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    
</LinearLayout>

下面用 RecyclerView 來展示一個水果列表倦挂,先建立一個水果 Fruit 類:

/**
 * 水果類
 * Created by KXwon on 2016/12/11.
 */

public class Fruit {
    private String name; // 水果名
    private int imageId; // 水果圖片id

    public Fruit(String name, int imageId) {
        this.name = name;
        this.imageId = imageId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getImageId() {
        return imageId;
    }

    public void setImageId(int imageId) {
        this.imageId = imageId;
    }
}

以及展示水果的布局 fruit_item.xml

<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <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_vertical"
        android:layout_marginLeft="10dp"/>

</LinearLayout>

接下來為 RecyclerView 準(zhǔn)備一個適配器,新建 FruitAdapter 類担巩,讓這個適配器繼承RecyclerView.Adapter方援,并將泛型指定為 FruitAdapter.ViewHolder。其中兵睛,ViewHolder 是在 FruitAdapter 中定義的一個內(nèi)部類肯骇,代碼如下:

/**
 * 水果適配器
 * Created by KXwon on 2016/12/11.
 */

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder>{

    private List<Fruit> mFruitList;

    /**
     * 構(gòu)造函數(shù),用于把要展示的數(shù)據(jù)源傳進來
     * @param mFruitList
     */
    public FruitAdapter(List<Fruit> mFruitList) {
        this.mFruitList = mFruitList;
    }

    /**
     * 創(chuàng)建ViewHolder實例
     * @param parent
     * @param viewType
     * @return
     */
    @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;
    }

    /**
     * 對RecyclerView子項的數(shù)據(jù)進行賦值
     * @param holder
     * @param position
     */
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Fruit fruit = mFruitList.get(position);
        holder.fruitImage.setImageResource(fruit.getImageId());
        holder.fruitName.setText(fruit.getName());
    }

    /**
     * 子項的數(shù)目
     * @return
     */
    @Override
    public int getItemCount() {
        return mFruitList.size();
    }

    /**
     * 內(nèi)部類祖很,ViewHolder要繼承自 RecyclerView.ViewHolder
     */
    public 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);
        }
    }
}

適配器準(zhǔn)備好了之后,可以開始使用 RecyclerView 了漾脂,activity 中的代碼如下:

public class RecyclerVewActivity extends AppCompatActivity {

    private List<Fruit> fruitList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler_view);

         // 初始化水果數(shù)據(jù)
        initFruits();
        // 獲取RecyclerView的實例
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        // LayoutManager用于指定RecyclerView的布局方式假颇,LinearLayoutManager表示線性布局
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        // 創(chuàng)建FruitAdapter的實例
        FruitAdapter adapter = new FruitAdapter(fruitList);
        // 設(shè)置適配器
        recyclerView.setAdapter(adapter);
    }

    private void initFruits() {
        for (int i = 0;i < 2;i++){
            Fruit apple = new Fruit("Apple",R.drawable.pic_apple);
            fruitList.add(apple);
            Fruit banana = new Fruit("Banana",R.drawable.pic_banana);
            fruitList.add(banana);
            Fruit orange = new Fruit("orange",R.drawable.pic_orange);
            fruitList.add(orange);
            Fruit watermelon = new Fruit("watermelon",R.drawable.pic_watermelon);
            fruitList.add(watermelon);
            Fruit grape = new Fruit("grape",R.drawable.pic_grape);
            fruitList.add(grape);
            Fruit pineapple = new Fruit("pineapple",R.drawable.pic_pineapple);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit("strawberry",R.drawable.pic_strawberry);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit("cherry",R.drawable.pic_cherry);
            fruitList.add(cherry);
            Fruit mango = new Fruit("mango",R.drawable.pic_mango);
            fruitList.add(mango);
        }
    }
}

運行效果如下:

RecyclerView運行效果

3.3.2 實現(xiàn)橫向滾動和瀑布流布局

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="60dp"
        android:layout_height="60dp"
        android:padding="5dp"
        android:layout_gravity="center_horizontal"/>

    <TextView
        android:id="@+id/fruit_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp"/>

</LinearLayout>

上述代碼中骨稿,把 LinearLayout 改成了垂直方向笨鸡,寬度設(shè)為 100dp姜钳,把 ImageViewTextView 設(shè)成了布局中水平居中,接下來修改 activity 中的代碼:

public class RecyclerVewActivity extends AppCompatActivity {

    private List<Fruit> fruitList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler_view);

        // 初始化水果數(shù)據(jù)
        initFruits();
        // 獲取RecyclerView的實例
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        // LayoutManager用于指定RecyclerView的布局方式形耗,LinearLayoutManager表示線性布局
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        // 設(shè)置布局橫向排列(默認(rèn)是縱向排列的)
        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        recyclerView.setLayoutManager(layoutManager);
        // 創(chuàng)建FruitAdapter的實例
        FruitAdapter adapter = new FruitAdapter(fruitList);
        // 設(shè)置適配器
        recyclerView.setAdapter(adapter);
    }
    ...
}  

Activity 中只加了一行代碼哥桥,調(diào)用 LinearLayoutManagersetOrientation() 方法來設(shè)置布局的排列方向,運行程序激涤,效果如下:

橫向 RecyclerView 效果

除了 LinearLayoutManager 之外拟糕,RecyclerView 還提供了 GridLayoutManagerStaggeredGridLayoutManager 兩種內(nèi)置的布局排列方式。GridLayoutManager 實現(xiàn)網(wǎng)格布局倦踢,StaggeredGridLayoutManager 實現(xiàn)瀑布流布局送滞。

接下來實現(xiàn)下瀑布流布局,首先修改 fruit_item.xml 中的代碼:

<LinearLayout
    xmlns: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">

    <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:layout_height="wrap_content"
        android:layout_gravity="left"
        android:layout_marginTop="10dp"/>

</LinearLayout>

上述代碼中辱挥,把 LinearLayout 的寬度設(shè)為 match_parent 因為瀑布流布局的寬度是根據(jù)布局的列數(shù)來自動適配的犁嗅,而不是一個固定值,把 TextView 設(shè)成了居左對齊晤碘,接下來修改 activity 中的代碼:

public class RecyclerVewActivity extends AppCompatActivity {

    private List<Fruit> fruitList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler_view);

        // 初始化水果數(shù)據(jù)
        initFruits();
        // 獲取RecyclerView的實例
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        // 創(chuàng)建StaggeredGridLayoutManager的實例(構(gòu)造函數(shù)中的兩個參數(shù):第一個指定布局的列數(shù)褂微,第二個指定布局的排列方向)
        StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);
        // 創(chuàng)建FruitAdapter的實例
        FruitAdapter adapter = new FruitAdapter(fruitList);
        // 設(shè)置適配器
        recyclerView.setAdapter(adapter);
    }

    private void initFruits() {
        for (int i = 0;i < 2;i++){
            for (int i = 0;i < 2;i++){
            Fruit apple = new Fruit(getRandomLengthName("Apple"),R.drawable.pic_apple);
            fruitList.add(apple);
            Fruit banana = new Fruit(getRandomLengthName("Banana"),R.drawable.pic_banana);
            fruitList.add(banana);
            Fruit orange = new Fruit(getRandomLengthName("orange"),R.drawable.pic_orange);
            fruitList.add(orange);
            Fruit watermelon = new Fruit(getRandomLengthName("watermelon"),R.drawable.pic_watermelon);
            fruitList.add(watermelon);
            Fruit grape = new Fruit(getRandomLengthName("grape"),R.drawable.pic_grape);
            fruitList.add(grape);
            Fruit pineapple = new Fruit(getRandomLengthName("pineapple"),R.drawable.pic_pineapple);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit(getRandomLengthName("strawberry"),R.drawable.pic_strawberry);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit(getRandomLengthName("cherry"),R.drawable.pic_cherry);
            fruitList.add(cherry);
            Fruit mango = new Fruit(getRandomLengthName("mango"),R.drawable.pic_mango);
            fruitList.add(mango);
        }
    }

    /**
     * 隨機生成水果名字的長度
     * @param name
     * @return
     */
    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();
    }
}

至此,已成功實現(xiàn)瀑布流效果了园爷,效果如下:

瀑布流布局效果

3.3.3 RecyclerView 的點擊事件

不同于 ListView 的是蕊梧,RecyclerView 并沒有提供類似 setOnItemClickListener() 這樣的注冊監(jiān)聽方法,而是需要給子項具體的 view 去注冊點擊事件腮介。
??
為實現(xiàn) RecyclerView 中注冊點擊事件肥矢,修改 FruitAdapter 中的代碼:

/**
 * 水果適配器
 * Created by KXwon on 2016/12/11.
 */

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder>{

    private List<Fruit> mFruitList;

    /**
     * 構(gòu)造函數(shù),用于把要展示的數(shù)據(jù)源傳進來
     * @param mFruitList
     */
    public FruitAdapter(List<Fruit> mFruitList) {
        this.mFruitList = mFruitList;
    }

    /**
     * 創(chuàng)建ViewHolder實例
     * @param parent
     * @param viewType
     * @return
     */
    @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.fruitView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                int position = holder.getAdapterPosition();
                Fruit fruit = mFruitList.get(position);
                ToastUtils.showShort("you clicked view"+ fruit.getName());
            }
        });
        // 為ImageView注冊點擊事件
        holder.fruitImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                int position = holder.getAdapterPosition();
                Fruit fruit = mFruitList.get(position);
                ToastUtils.showShort("you clicked image"+ fruit.getName());
            }
        });
        return holder;
    }

    /**
     * 對RecyclerView子項的數(shù)據(jù)進行賦值
     * @param holder
     * @param position
     */
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Fruit fruit = mFruitList.get(position);
        holder.fruitImage.setImageResource(fruit.getImageId());
        holder.fruitName.setText(fruit.getName());
    }

    /**
     * 子項的數(shù)目
     * @return
     */
    @Override
    public int getItemCount() {
        return mFruitList.size();
    }

    /**
     * 內(nèi)部類叠洗,ViewHolder要繼承自 RecyclerView.ViewHolder
     */
    public class ViewHolder extends RecyclerView.ViewHolder{
        View fruitView; // 添加fruitView變量來保存子項最外層布局的實例
        ImageView fruitImage;
        TextView fruitName;

        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,在 ViewHolder 中添加了 fruitView 變量來保存子項最外層布局的實例灭抑,然后在 onCreateViewHolder() 方法中注冊點擊事件就可以了十艾。

這里分別為最外層布局和 ImageView 注冊了點擊事件。RecyclerView 的強大之處在于可以輕松實現(xiàn)子項中任意控件或布局的點擊事件腾节。
??
運行程序忘嫉,并點擊香蕉的圖片部分,效果如下:

點擊香蕉的圖片部分

點擊菠蘿的文字部分案腺,由于 TextView 沒有注冊監(jiān)聽事件庆冕,因此點擊文字會被子項的最外層布局捕獲到,效果如下:

點擊菠蘿的文字部分

3.4 編寫界面的最佳實踐

3.4.1 制作 Nine_Patch 圖片

若項目中有一張氣泡樣式的圖片 message_left.png劈榨,如圖所示:

message_left.png

若將這張圖片設(shè)置為一個 LinearLayout 的背景圖片访递,代碼如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/message_left">

</LinearLayout>

LinearLayout 的寬度指定為 match_parent,然后將它的背景圖設(shè)置為 message_left同辣,運行效果如圖:

氣泡被均勻拉伸的效果

可以看到拷姿,由于 message_left 的寬度不足以填滿整個屏幕的寬度惭载,整張圖片被均勻地拉伸了!這種效果非常差响巢,這時就可以使用 Nine-Patch 圖片來進行改善描滔。

Android sdk 目錄下有一個 tools 文件夾,在這個文件夾中找到 draw9patch.bat 文件踪古, 可使用它來制作 Nine-Patch 圖片含长。

雙擊打開 draw9patch.bat 文件,在導(dǎo)航欄點擊 File→Open 9-patch 將準(zhǔn)備好的圖片 message_left.png 加載進來灾炭,如圖所示:

使用 draw9patch 編輯 message_left 圖片

可以在圖片的四個邊框繪制一個個的小黑點茎芋,在上邊框和左邊框繪制的部分就表示當(dāng)圖片需要拉伸時就拉伸黑點標(biāo)記的區(qū)域,在下邊框和右邊框繪制的部分則表示內(nèi)容會被放置的區(qū)域蜈出。使用鼠標(biāo)在圖片的邊緣拖動就可以繪制了田弥,按住 Shift 鍵拖動可以進行擦除,完成后效果如圖所示:

繪制完后的 message_left 圖片

最后保存即可铡原。用制作好的圖片替換掉之前的 message_left.png 圖片偷厦,重新運行程序,效果如圖:

氣泡只拉伸繪制區(qū)域的效果

接下來進入實戰(zhàn)環(huán)節(jié)燕刻。

3.4.2 編寫精美的聊天界面

上面制作的 message_left.9.png 可以作為收到消息的背景圖只泼,再制作一張 message_right.9.png 作為發(fā)出消息的背景圖。
??
首先在 app/buiild.gradle 當(dāng)中添加要用到的 RecyclerView 依賴庫:

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'
}

接下來編寫主界面卵洗,編寫主界面 activity_ui_best_practice.xml 中的代碼如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#d8e0e8">
    
    <android.support.v7.widget.RecyclerView
        android:id="@+id/msg_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        
        <EditText
            android:id="@+id/et_input_text"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="Type something here"
            android:maxLines="2"/>

        <Button
            android:id="@+id/btn_send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Send" />
        
    </LinearLayout>

</LinearLayout>

上述代碼在主界面放置了一個 RecyclerView 來顯示聊天的消息內(nèi)容请唱,放置了一個 EditText 用于輸入消息,放置了一個 Button 用于發(fā)送消息过蹂。
??
然后定義消息的實體類 Msg 如下所示:

/**
 * 消息實體類
 * Created by KXwon on 2016/12/11.
 */

public class Msg {
    
    public static final int TYPE_RECEIVED = 0; // 收到的消息類別

    public static final int TYPE_SENT = 1;     // 發(fā)出的消息類別
    
    private String content; // 消息內(nèi)容
    
    private int type;       // 消息類型

    public Msg(String content, int type) {
        this.content = content;
        this.type = type;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }
}

接著編寫 RecyclerView 子項的布局十绑,新建 msg_item.xml,如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="10dp">

    <!-- ************  收到的消息居左對齊  ************ -->
    <LinearLayout
        android:id="@+id/left_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:background="@drawable/message_left">

        <TextView
            android:id="@+id/left_msg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="10dp"
            android:textColor="#fff"/>

    </LinearLayout>

    <!-- ************  發(fā)送的消息居右對齊  ************ -->
    <LinearLayout
        android:id="@+id/right_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:background="@drawable/message_right">

        <TextView
            android:id="@+id/right_msg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="10dp" />

    </LinearLayout>

</LinearLayout>

接下來創(chuàng)建 RecyclerView 的適配器酷勺,新建 MsgAdapter本橙,如下:

/**
 * 消息適配器
 * Created by KXwon on 2016/12/11.
 */

public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.ViewHolder>{

    private List<Msg> mMsgList;

    public MsgAdapter(List<Msg> mMsgList) {
        this.mMsgList = mMsgList;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.msg_item,parent,false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Msg msg = mMsgList.get(position);
        if (msg.getType() == Msg.TYPE_RECEIVED){
            // 若是收到的消息,則顯示左邊的布局消息脆诉,將右邊的消息布局隱藏
            holder.leftLayout.setVisibility(View.VISIBLE);
            holder.rightLayout.setVisibility(View.GONE);
            holder.leftMsg.setText(msg.getContent());
        }else if (msg.getType() == Msg.TYPE_SENT){
            // 若是發(fā)送的消息甚亭,則顯示右邊的布局消息,將左邊的消息布局隱藏
            holder.leftLayout.setVisibility(View.GONE);
            holder.rightLayout.setVisibility(View.VISIBLE);
            holder.rightMsg.setText(msg.getContent());
        }
    }

    @Override
    public int getItemCount() {
        return mMsgList.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder{

        LinearLayout leftLayout, rightLayout;

        TextView leftMsg, rightMsg;

        public ViewHolder(View view) {
            super(view);
            leftLayout = (LinearLayout) view.findViewById(R.id.left_layout);
            rightLayout = (LinearLayout) view.findViewById(R.id.right_layout);
            leftMsg = (TextView) view.findViewById(R.id.left_msg);
            rightMsg = (TextView) view.findViewById(R.id.right_msg);
        }
    }

}

最后修改 activity 中的代碼击胜,來為 RecyclerView 初始化一些數(shù)據(jù)亏狰,并給發(fā)送消息加入事件響應(yīng),如下:

public class UIBestPracticeActivity extends AppCompatActivity {

    private List<Msg> msgList = new ArrayList<>();

    private EditText et_input_text;
    private Button btn_send;

    private RecyclerView msgRecyclerView;
    private MsgAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ui_best_practice);

        initMsg(); // 初始化消息數(shù)據(jù)

        et_input_text = (EditText) findViewById(R.id.et_input_text);
        btn_send = (Button) findViewById(R.id.btn_send);

        msgRecyclerView = (RecyclerView) findViewById(R.id.msg_recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        msgRecyclerView.setLayoutManager(layoutManager);
        adapter = new MsgAdapter(msgList);
        msgRecyclerView.setAdapter(adapter);

        btn_send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String content = et_input_text.getText().toString();
                if (!"".equals(content)){
                    Msg msg = new Msg(content,Msg.TYPE_SENT);
                    msgList.add(msg);
                    // 當(dāng)有新消息時潜的,刷新RecyclerView中的顯示
                    adapter.notifyItemInserted(msgList.size() - 1);
                    // 將RecyclerView定位到最后一行
                    msgRecyclerView.scrollToPosition(msgList.size() - 1);
                    // 清空輸入框中的內(nèi)容
                    et_input_text.setText("");
                }
            }
        });
    }

    private void initMsg() {
        Msg msg1 = new Msg("Hello world!",Msg.TYPE_RECEIVED);
        msgList.add(msg1);
        Msg msg2 = new Msg("Hello. Who is that?",Msg.TYPE_SENT);
        msgList.add(msg2);
        Msg msg3 = new Msg("骚揍。。啰挪。",Msg.TYPE_SENT);
        msgList.add(msg3);
        Msg msg4 = new Msg("This is 逗逼. Nice talking to you",Msg.TYPE_RECEIVED);
        msgList.add(msg4);
    }
}

這樣一個可以輸入和發(fā)送消息的聊天界面所有的工作就都完成了信不,運行效果如下:

聊天界面

至此,第三章筆記就到這亡呵,下篇文章將學(xué)習(xí)碎片的知識抽活。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市锰什,隨后出現(xiàn)的幾起案子下硕,更是在濱河造成了極大的恐慌,老刑警劉巖汁胆,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梭姓,死亡現(xiàn)場離奇詭異,居然都是意外死亡嫩码,警方通過查閱死者的電腦和手機誉尖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铸题,“玉大人铡恕,你說我怎么就攤上這事《洌” “怎么了探熔?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長烘挫。 經(jīng)常有香客問我诀艰,道長,這世上最難降的妖魔是什么饮六? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任其垄,我火速辦了婚禮,結(jié)果婚禮上喜滨,老公的妹妹穿的比我還像新娘捉捅。我一直安慰自己,他們只是感情好虽风,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布棒口。 她就那樣靜靜地躺著,像睡著了一般辜膝。 火紅的嫁衣襯著肌膚如雪无牵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天厂抖,我揣著相機與錄音茎毁,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛七蜘,可吹牛的內(nèi)容都是我干的谭溉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼橡卤,長吁一口氣:“原來是場噩夢啊……” “哼扮念!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起碧库,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤柜与,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后嵌灰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弄匕,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年沽瞭,在試婚紗的時候發(fā)現(xiàn)自己被綠了迁匠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡秕脓,死狀恐怖柒瓣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吠架,我是刑警寧澤芙贫,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站傍药,受9級特大地震影響磺平,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拐辽,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一拣挪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧俱诸,春花似錦菠劝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至园骆,卻和暖如春舔痪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锌唾。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工锄码, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓滋捶,卻偏偏與公主長得像痛悯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子炬太,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容