第一行代码 第三章 UI开发

第三章 UI开发

3.2 常用控件

初学暂时使用的布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</LinearLayout>

1.TextView

TextView可以说是Android中最简单的一个控件,它主要用于在界面上显示一段文本信息,比如第一章中的HelloWorld。下面是TextView的更多用法:

<!--
这里我们利用TextView做一个跑马灯
android:singleLine:内容单行显示
android:focusable:是否可以获取焦点
android:focusableInTouchMode:用于控制视图在触摸模式下是否可以聚焦
android:ellipsize:在哪里省略文本(marquee滚动、start省略号在前面、end省略号在后面、middle省略号在中间、none)
android:marqueeRepeatLimit:字幕动画重复的次数marquee_forever无限次数
 以下代码实现跑马灯
-->
    <TextView
        android:id="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:gravity="center"
        android:text="HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorld"
        android:background="#BBC8C3"
        android:textStyle="italic"
        android:textSize="24sp"
        android:textColor="#229000FF"
        android:shadowColor="@color/purple_700"
        android:shadowDx="15.0"
        android:shadowDy="15.0"
        android:shadowRadius="10.0"
        android:singleLine="true"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:ellipsize="marquee"
        android:marqueeRepeatLimit="marquee_forever"/>

属性含义
android:id该组件的唯一标识符
android:layout_widthmatch_parent(当前控件的大小和父布局一样)、fill_parent(意思同上)、wrap_content(当前控件的大小刚好包含住里面的内容)
android:layout_height同上
android:text指定在TextView中显示的文本内容(文字默认是居左上角对齐)
android:gravity指定组件内文字的对齐方式可选top\bottom\left\right\center等;可以用"|"*来同时指定多个值;*center的效果等同于center_vertical
android:textSize设置文字的大小(sp为单位)
android:textColor设置文字颜色
android:textStyle设置字体风格italic(斜体),bold(加粗),normal(默认)
android:background设置控件的背景颜色
android:shadowColor设置阴影颜色,需要与shadowRadius一起使用
android:shadowDx设置阴影再说水平方向的偏移,就是水平方向阴影开始的横坐标位置
android:shadowDy设置阴影再说竖直方向的偏移,就是水平方向阴影开始的纵坐标位置
android:shadowRadius设置阴影的模糊程序,设为0.1就变成字体颜色,建议使用3.0

2.Button

Button是程序用于用户进行交互的一个重要控件

<!--
注意系统会对Button中的所有英文字母自动进行大写转换,可以使用android:textAllCaps="false"禁止这个特性
-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns: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"
    android:orientation="vertical">
    <Button
        android:id="@+id/button1"
        android:text="Button"
        android:textColor="@color/black"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
/>
</LinearLayout>

为了响应点击事件,我们要在MainActivity中为Button的点击事件注册监听器

//1、点击事件 2、长按事件 3、触摸事件
package com.example.test;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    public static final String TAG="MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button1= (Button)findViewById(R.id.button1);
        //1、点击事件
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d(TAG, "onClick: ");
            }
        });
        //2、长按事件
        button1.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                Log.d(TAG, "onLongClick: ");
                return false;
            }
        });
        //3、触摸事件
        button1.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                Log.d(TAG, "onTouch: ");
                return false;
            }
        });
    }
}

如果你不喜欢使用匿名类的方式来注册监听器

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button1= (Button)findViewById(R.id.button1);
        //点击事件
        button1.setOnClickListener(this);
    }
    @Override
    public void onClick(View v){
		switch(v.getId()){
            case R.id.button1:
                //在此添加逻辑
                break;
            default:
                break;
        }
    }
}

3.EditText

EditText是程序用于和用户进行交互的另一个重要控件,它允许用户在控件里输入和编辑内容,并可以在程序中对这些内容进行处理。

<!--
android:hint:指定了输入框中的提示性文本
android:textColorHint输入提示文字的颜色
android:InputType输入类型
android:maxLines:指定最大行数,当输入内容超过最大行数时,文本就会向上滚动
android:drawableXXX在输入框的指定方位添加图片
android:drawablePadding设置图片与输入内容的间距
android:background背景色
andorid:paddingXXX:与整个输入框的距离
-->
    <EditText
        android:id="@+id/edit_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="提示文本"
        android:maxLines="2"/>

结合使用EditText与Button:点击按钮来获取EditText中输入的内容。修改MainActivity代码:

//MainActivity.java
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    private EditText editText;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        /*
        * 通过点击按钮来获取EditText中输入的内容
        * */
        Button button=(Button) findViewById(R.id.button1);
        editText=(EditText) findViewById(R.id.edit_text);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                switch (view.getId()){
                    case R.id.button1:
                        //通过EditText的getText方法后去输入的内容
                        String inputText=editText.getText().toString();	             					          		   Toast.makeText(MainActivity.this,inputText,Toast.LENGTH_SHORT).show();
                        break;
                    default:
                        break;
                }
            }
        });
    }
}

4.ImageView

ImageView是用于在界面上展示图片的一个控件,它可以让我们的程序界面变得更加丰富多彩。我们所要展示的图片通常存放在以“drawable”开头的目录下,目前项目中有一个drawable目录,但是这个目录没有指定分辨率,所以一般不使用它来放置图片,这里在res目录下新建一个drawable-xhdpi目录,然后将我们要展示的照片复制到该目录中。

<!--ImageView每个只能显示一张图片
android:src:图片存放的路径,设置图片资源
android:scaleType:设置图片缩放类型
	fitStart:保持宽高比缩放图片直到较长的边与image的边长相等,缩放完成(imageview左上角)
	fitCenter:默认值,同上,缩放后位于中间
 	fitXY:对图像的横纵方向进行独立缩放,使得该图片完全使用ImageView,但图片的宽高比可能会改变
	center:保持原图的大小,显示在imageView的中心,当原图大小大于imageview时会进行裁剪处理
	centerCrop:保持宽高比缩放图片,直到完全覆盖ImageView,可能会出现图片的显示不完全
	centerInside:保持宽高比缩放图片,直到ImageView能够完全的显示图片
	matrix:不改变原图的大小,从ImageView的左上角开始绘制原图,原图超过imageView的部分裁剪处理

以下三个设置当有一边达到最大宽度或者高度时,另外一边等比缩放
android:maxHeight:最大高度
android:maxWidth:最大宽度
android:adjustViewBounds="true"调整View的界限
   -->
    <ImageView
        android:id="@+id/image_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/img1" />

动态更改ImageView中的图片

//先获取ImageView实例
imageView=(ImageView) findViewById(R.id.image_view);
//在需要更改照片的事件中添加: 
imageView.setImageResource(R.drawable.img2)

5.ProgressBar

ProgressBar用于在界面上显示一个进度条,表明程序正在加载一些数据,运行之后会看到屏幕中有一个圆形的进度条正在旋转。

<!--
 android:visibility:控制是否可见visible(可见),invisible(不可见但仍然占据原来的位置),gone(不可见且不占用屏幕空间)
 style="@style/Widget.AppCompat.ProgressBar.Horizontal":设置水平进度条
android:max="100":进度条的最大值
android:progress:进度条已完成进度值
android:indeterminate:如果设置成true,则进度条不精确显示进度
style="?android:attr/progressBarStyleHorizontal"水平进度条
  -->
    <ProgressBar
        android:id="@+id/progress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="visible"
        style="?android:attr/progressBarStyleHorizontal"
        android:max="100"/>
//设置进度条显示
progressBar=(ProgressBar)findViewById(R.id.progress);
progressBar.setVisibility(View.VISIBLE);
progressBar=(ProgressBar)findViewById(R.id.progress);
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.button1:
                //动态修改水平进度条数据,每点击一次按钮进度条就加10
                int projress=progressBar.getProgress();
                projress=projress+10;
                progressBar.setProgress(projress);
                break;
            default:
                break;
        }
    }	
});

6.AlertDialog

AlertDialog可以在当前的界面弹出一个对话框,这个对话框是置顶于所以界面元素之上的,能够屏蔽掉其他控件的交互能力,因此AlertDialog一般都是用于提示一些非常重要的内容或者警告信息,比如为了防止用户误删重要内容,在删除前弹出一个确认对话框

使用方式介绍

AlertDialog.Builder builder=new AlertDialog.Builder(context);创建AlertDialog实例
Builder.setIcon(int iconid);添加ICON
Builder.setTitle(CharSequence title);添加标题
Builder.setMessage(CharSequence message);添加消息
Builder.setView(View view);设置自定义布局
Builder.create();创建Dialog
Builder.show();显示对话框
Builder.setCancelable();设置是否可以用back键返回
setPositiveButton确定按钮
setNegativeButton取消按钮
setNeutralButton中间按钮

代码示例:

	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main); 
        Button button=(Button) findViewById(R.id.button1);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
/*
   首先通过AlertDialog.Builder创建一个AlertDialog的实例,然后可以为这个话题框设置标题、内容、
   可否使用back键关闭对话框等属性,接下来调用setPositiveButton()方法为对话框设置确定按钮的点击事件
  调用setNegativeButton()方法设置取消按钮的点击事件,最后调用show()方法将对话框显示出来
*/
                switch(view.getId()){
                    case R.id.button1:
                        AlertDialog.Builder dialog=new AlertDialog.Builder(MainActivity.this);
                        dialog.setTitle("This is a dialog");
                        dialog.setMessage("Something important.");
                        //设置是否可以用back键返回
                        dialog.setCancelable(false);
                        dialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {
                            }
                        });
                        dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {
                            }
                        });
                        dialog.show();
                        break;
                    default:
                        break;
                }
            }
        });

    }
}

进阶做法:自定义对话框内的内容

布局文件一(主布局)activity_second.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:orientation="vertical">
    <Button
        android:id="@+id/bu1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="显示对话框"
        android:onClick="xyClick"/>
</LinearLayout>

布局文件二:
dialog_view.xml
<?xml version="1.0" encoding="utf-8"?>
<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="#ffff00">

    <ImageView
        android:src="@mipmap/ic_launcher"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="哈哈哈,天气很好"/>
</LinearLayout>
package com.example.test;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class SecondActivity extends AppCompatActivity {
    private static final String TAG="SecondActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
    }
	//点击事件的逻辑,需要放在"button.setOnClickListener(new View.OnClickListener() {}"里
    public void xyClick(View view){
        //添加自定义布局
        View dialogview=getLayoutInflater().inflate(R.layout.dialog_view,null);

        //1、构建各种参数
       AlertDialog.Builder builder= new AlertDialog.Builder(this);
       builder.setIcon(R.mipmap.ic_launcher)
               .setTitle("这是对话框")//点击按钮显示的内容的标题
               .setMessage("天气预报")//显示的内容
               .setView(dialogview)//显示新的布局,这里我们设置了对话框的视图
               //确定
               .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                   @Override
                   public void onClick(DialogInterface dialogInterface, int i) {
                       Log.d(TAG, "onClick: 点击确定");
                   }
               })
               //取消
               .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                   @Override
                   public void onClick(DialogInterface dialogInterface, int i) {
                       Log.d(TAG, "onClick: 点击取消");
                   }
               })
               //中间
               .setNeutralButton("中间", new DialogInterface.OnClickListener() {
                   @Override
                   public void onClick(DialogInterface dialogInterface, int i) {
                       Log.d(TAG, "onClick: 点击中间");
                   }
               })
               .create()
               .show();
    }
}

7.ProgressDialog

ProgressDialog和AlertDialog有点类似,都可以在界面上弹出一个对话框,都能够屏蔽掉其他控件的交互能力。不同的是,ProgressDialog会在对话框中显示一个进度条,一般用于标识当前操作比较耗时,让用户耐心地等待。

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button=(Button) findViewById(R.id.button1);
        editText=(EditText) findViewById(R.id.edit_text);
        progressBar=(ProgressBar)findViewById(R.id.progress);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                /*
                * 这里也是先构建一个ProgressDialog对象,然后同样可以设置标题,内容
                * 可否取消等属性最后调用show()方法显示出来
                * progressDialog.setCancelable(true)
               	* 如果填了false则不能通过back键取消,需要在数据加载完成后
               	* 调用ProgressDialog的dismiss()方法来关闭对话框,否则ProgressDialog会一直在
                * */
                switch(view.getId()){
                    case R.id.button1:
                        ProgressDialog progressDialog=new ProgressDialog(MainActivity.this);
                        progressDialog.setTitle("This is ProgressDialog");
                        progressDialog.setMessage("Loading......");
                        progressDialog.setCancelable(true);
                        progressDialog.show();
                        break;
                    default:
                        break;
                }
            }
        });
    }

8.ListView

当我们的程序中有大量的数据需要展示时,就可以借助ListView来实现。

ListView的简单用法

activity_main.xml:

<LinearLayout
    xmlns: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);
        //数组中的数据是无法直接传递给ListView的,需要借助适配器来完成。
        //适配器构造函数中依次传入当前上下文、ListView子项的布局id,以及要适配的数据
        ArrayAdapter<String> adapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_list_item_1, data);
        ListView listView = findViewById(R.id.list_view);
        listView.setAdapter(adapter);
    }
}
定制LsitView的界面

示例

新建类Fruit,作为ListView适配器的适配类型

package com.example.listviewtest;

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

}

为ListView的子项指定一个我们自定义的布局,在layout目录下新建fruit_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="horizontal"
	android:layout_width="match_parent"
	android:layout_height="match_parent">

	<ImageView
		android:id="@+id/fruit_image"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content" />

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

</LinearLayout>

创建一个自定义的适配器FruitAdapter

public class FruitAdapter extends ArrayAdapter<Fruit> {

    private int resourceId;
    //重写父类的构造函数
    //将上下文、ListView子项布局的id和数据都传递进来
    public FruitAdapter(Context context, int textViewResourceId,
                        List<Fruit> objects) {
        super(context, textViewResourceId, objects);
        resourceId = textViewResourceId;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // 获取当前项的Fruit实例
        Fruit fruit = getItem(position); 
        //LayoutInflater.from(this);  获取LayoutInflater实例
        //使用LayoutInflater为子项加载我们传入的布局
        //inflate的三个参数:要转换的xml布局、给加载好的布局再添加一个父布局、
        //是否让父布局的layout属性失效
        View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
        TextView fruitName = view.findViewById(R.id.fruit_name);
        ImageView fruitImage = view.findViewById(R.id.fruit_image);
        fruitName.setText(fruit.getFruitName());
        fruitImage.setImageResource(fruit.getImageId());
        return view;
}

修改MainActivity

public class MainActivity extends AppCompatActivity {

    private List<Fruit> fruitList = new ArrayList<>();
//    private String[] data = {"Apple","Banana","Oraneg","Watermelon","Pear","Grape","Pineapple","Strawberry","Cherry","Mango","Apple","Banana","Oraneg","Watermelon","Pear","Grape","Pineapple","Strawberry","Cherry","Mango"};

    @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 initFruits() {
        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.banana_pic);
            fruitList.add(banana);
            Fruit orange = new Fruit("Orange", R.drawable.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new Fruit("Watermelon", R.drawable.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit("Pear", R.drawable.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit("Grape", R.drawable.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit("Mango", R.drawable.mango_pic);
            fruitList.add(mango);
        }
    }
}
提升ListView的运行效率

修改FruitAdapter

@Override
//convertView用于将之前加载好的布局进行缓存
public View getView(int position, View convertView, ViewGroup parent) {
	Fruit fruit = getItem(position); // 获取当前项的Fruit实例
	View view;
	ViewHolder viewHolder;
	if (convertView == null) {
		view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
		viewHolder = new ViewHolder();
		viewHolder.fruitImage = (ImageView) view.findViewById (R.id.fruit_image);
		viewHolder.fruitName = (TextView) view.findViewById (R.id.fruit_name);
		view.setTag(viewHolder); // 将ViewHolder存储在View中
	} else {
		view = convertView;
		viewHolder = (ViewHolder) view.getTag(); // 重新获取ViewHolder
	}
	viewHolder.fruitImage.setImageResource(fruit.getImageId());
	viewHolder.fruitName.setText(fruit.getName());
	return view;
}
//新建一个内部类ViewHolder,用于对控件的实例进行缓存
class ViewHolder {
	ImageView fruitImage;
	TextView fruitName;
}
ListView的点击事件

修改MainActivty

@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);
    //使用setOnItemClickListener()方法为ListView注册一个监听器,当用户点击了
    //ListView中的任何一个子项是,就会回调onItemClick()方法
	listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
		@Override
		public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
			Fruit fruit = fruitList.get(position);
			Toast.makeText(MainActivity.this,fruit.getName(),Toast.LENGTH_SHORT).show();
		}
	});
}

9.RecyclerView

RecyclerView的基础用法

导入依赖:

implementation 'androidx.recyclerview:recyclerview:1.2.8'

修改activity_main.xml中的代码:

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
/>

这里我们直接从ListView项目中把照片、Fruit类和fruit_item.xml复制过来(具体代码可以看ListView部分的代码)

接下来为RecyclerView准备一个适配器,新建FruitAdapter类,让这个适配器继承自RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder。

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
    private List<Fruit> mFruitList;
    
    static class ViewHolder extends RecyclerView.ViewHolder {
        ImageView fruitImage;
        TextView fruitName;
 
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            fruitImage = itemView.findViewById(R.id.fruit_image);
            fruitName = itemView.findViewById(R.id.fruit_name);
        }
    }
 	
    public FruitAdapter(List<Furit> FruitList){
        mFruitList = FruitList
    }
    
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }
 
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        Fruit fruit = mFruitList.get(position);
        holder.fruitName.setText(fruit.getFruitName());
        holder.fruitImage.setImageResource(fruit.getFruitImage());
    }
 
    @Override
    public int getItemCount() {
        return fruitList.size();
    }
}

修改MainActivity

public class MainActivity extends AppCompatActivity {
 
    private List fruitList = new ArrayList<Fruit>();
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        RecyclerView recyclerView = findViewById(R.id.recycler_view);
        //指定RecyclerView的布局方式,此处是线性布局
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(linearLayoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
    }
 
    private void initFruits(){
        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.banana_pic);
            fruitList.add(banana);
            Fruit orange = new Fruit("Orange", R.drawable.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new Fruit("Watermelon", R.drawable.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit("Pear", R.drawable.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit("Grape", R.drawable.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit("Mango", R.drawable.mango_pic);
            fruitList.add(mango);
        }
    }
}

运行程序可以看到RecyclerView实现了和ListView几乎一模一样的效果。

实现横向滚动和瀑布流布局

横向滚动

RecyclerView不仅能实现纵向滚动,还能实现ListView做不到的横向滚动。

首先我们对fruit_item布局进行修改:将LinearLayout改成垂直方向排列,并把宽度设为100dp

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

修改MainActivity:

RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(linearLayoutManager);

瀑布流布局(StaggeredGridLayoutManager)

瀑布流布局应该将fruit_item.xml中的LinearLayout的宽度改为match_parent,因为瀑布流布局的宽度应该是根据布局的列数来自动适配的,而不是一个固定值。

修改fruit_item.xml

<!--宽度改为match_parent-->
<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>

修改MainActivity

在onCreate()方法中,我们创建了一个StaggeredGridLayoutManager的实例。StaggeredGridLayoutManager的构造函数接收两个参数,第一个参数用于指定布局的列数,传入3表示会把布局分为3列;第二个参数用于指定布局的排列方式,传入StaggeredGridLayoutManager.VERTICAL表示会让布局纵向排列,最后再把创建好的实例设置到RecyclerView当中。

RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(linearLayoutManager);

由于瀑布流布局需要各个子项的高度不一致才能看出明显的效果,为此我们增加getRandomLengthName()方法去创造不同长度的水果名,这样子项的高度也就各不相同了。

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

在初始化水果数据的时候,我们应该按照下面的方式来创建水果对象

for (int i = 0; i < 2; i++) {
	Fruit apple = new Fruit(getRandomLengthName("Apple"), R.drawable.apple_pic);
	fruitList.add(apple);
	Fruit banana = new Fruit(getRandomLengthName("Banana"), R.drawable.banana_pic);
	fruitList.add(banana);
}
RecyclerView的点击事件

不同于ListView,RecyclerView没有提供类似于setOnItemClickListener()的注册监听器方法,需要我们自己给子项具体的View去注册点击事件。

修改FruitAdapter中的代码

在ViewHolder 中添加了fruitView变量来保存子项最外层布局的实例,然后在onCreateViewHolder()方法中注册点击事件。

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

为最外层布局和ImageView注册点击事件

public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
    final ViewHolder viewHolder = new ViewHolder(view);
    viewHolder.fruitView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            int position = viewHolder.getAdapterPosition();
            Fruit fruit = mFruitList.get(position);
            Toast.makeText(v.getContext(),"你点击了"+fruit.getFruitName()+"子项外层布局",Toast.LENGTH_SHORT).show();
        }
    });
    viewHolder.fruitImage.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            int position = viewHolder.getAdapterPosition();
            Fruit fruit = mFruitList.get(position);
            Toast.makeText(v.getContext(),"你点击了"+fruit.getFruitName()+"图片部分",Toast.LENGTH_SHORT).show();
        }
    });
    return viewHolder;
}

3.3 4种基本布局

布局是一种可用于放置很多控件的容器,它可以按照一定的规律调整内部控件的位置,从而编写出精美的界面,布局里面除了放置控件之外,还可以放置布局,通过多层布局的嵌套,可以完成一些比较复杂的界面实现

在这里插入图片描述

线性布局LinearLayout

这个布局会将它所包含的控件在线性方向上依次排列(android:orientation=“vertical”垂直方向上的线性排列),(android:orientation=“horizontal”水平方向上的线性排列),注意如果是水平线性排列的话内部控件的宽度不能为match_parent,不然单独一个控件会将整个水平方向占满。

代码示例:

<!--LinearLayout布局-->
<!--
android:orientation="horizontal",设置方向为水平方向(vertical为垂直方向)
android:layout_gravity,只有垂直方向的对齐方式才会生效(top,bottom,center_vertical)
android:layout_weight:这个属性允许我们使用比例的方式来指定控件的大小(系统根据总加起来的值再给每部分占比)
    当有layout_weight属性时,layout_width中要设置为0dp,这是一种比较规范的写法。
-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
>
    <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="wrap_content"
		android:layout_height="wrap_content"
		android:text="Send"/>
</LinearLayout>

常见属性

orientation布局中组件的排列方式
gravity控制组件所包含的子元素的对齐方式,可多个组合(center|bottom、center、center_vertical等等)
layout_gravity控制该组件在父容器里的对齐方式
background为该组件设置一个背景图片,或者是直接用颜色覆盖
divider分割线
showDividers设置分割线所在的位置,none(无),beginning(开始),end(结束),middle(每两个组件间)
dividerPadding设置分割线的padding
layout_weight(权重)该属性是用来等比例的划分区域,权重分配的是剩余空间,如果有一个组件属性为match_parent,则其他会被覆盖,如果为垂直方向则设置高,如果为水平方向则设置宽

相对布局RelativeLayout

和LinearLayout的排列规则不同,RelativeLayout会随意一些,它可以通过相对定位的方式让控件出现在布局的任何位置。

代码示例:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="100dp">
   <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="Button1"/>

    <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 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/button4"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_alignParentRight="true"
		android:layout_alignParentBottom="true"
		android:text="Button 5"/>
</RelativeLayout>

常见属性:

根据父容器定位属性作用
layout_alignParentLeft左对齐
layout_alignParentRight右对齐
layout_alignParentTop顶部对齐
layout_alignParentBottom底部对齐
layout_centerHorizontal水平居中
layout_centerVertical垂直居中
layout_centerInParent中间位置
根据兄弟组件定位作用
layout_toLeftOf放置于参考组件的左边
layout_toRightOf放置于参考组件的右边
layout_above放置于参考组件的上面
layout_below放置于参考组件的下面
layout_alignTop对齐参考组件的上边界
layout_alignBottom对齐参考组件的下边界
layout_alignLeft对齐参考组件的左边界
layout_alignRight对齐参考组件的右边界
通用属性作用
layout_margin上下左右偏移
layout_marginLeft左偏移
layout_marginRight右偏移
layout_marginBottom下偏移
layout_marginTop上偏移
设置组件内部元素的边距作用
padding上下左右边距
paddingBottom下边距
paddingLeft左边距
paddingRight右边距
paddingTop上边距

帧布局FrameLayout

这种布局所有的控件都会摆放在布局的左上角。我们也可以使用layout_gravity属性来指定控件在布局中的对齐方式

代码示例:

<?xml version="1.0" encoding="utf-8"?>
<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:layout_gravity="left"
		android:text="This is TextView"/>
    
    <ImageView
		android:id="@+id/button"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_gravity="right"
		android:src="@mipmap/ic_launcher"/>
    
</FrameLayout>

百分比布局

在这种布局中,我们可以不再使用wrap_content、match_parent等方式来指定控件的大小,而是允许直接指定控件在布局中所占的百分比。

要使用百分比布局,首先我们要在项目的build.gradle中添加百分比布局库的依赖。打开app/build.gradle文件,在dependencies闭包中添加如下内容:

implementation 'androidx.percentlayout:percentlayout:1.0.0'

代码示例:

<?xml version="1.0" encoding="utf-8"?>
<!--PercentFrameLayout布局-->
<androidx.percentlayout.widget.PercentFrameLayout
    xmlns: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">

    <Button
        android:id="@+id/button1"
        android:text="button1"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_gravity="left|top"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>

    <Button
        android:id="@+id/button2"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"
        android:text="button2"
        android:layout_gravity="right|top" />
    <Button
        android:id="@+id/button3"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"
        android:text="button3"
        android:layout_gravity="left|bottom"/>
    <Button
        android:id="@+id/button4"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"
        android:text="button4"
        android:layout_gravity="right|bottom"/>

</androidx.percentlayout.widget.PercentFrameLayout>


<?xml version="1.0" encoding="utf-8"?>
<!--PercentRelativeLayout布局-->
<androidx.percentlayout.widget.PercentRelativeLayout
    xmlns: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">

    <Button
        android:id="@+id/button1"
        android:text="button1"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_alignParentBottom="true"
        app:layout_widthPercent="20%"
        app:layout_heightPercent="20%"/>

    <Button
        android:id="@+id/button2"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_widthPercent="20%"
        app:layout_heightPercent="20%"
        android:text="button2"
        android:layout_above="@id/button1"/>
    <Button
        android:id="@+id/button3"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_widthPercent="20%"
        app:layout_heightPercent="20%"
        android:text="button3"
        android:layout_above="@id/button2"/>

</androidx.percentlayout.widget.PercentRelativeLayout>

3.4 创建自定义控件

空间和布局的继承结构

在这里插入图片描述

引入布局

引入布局可以解决代码的大量重复问题

创建一个自定义的标题栏

新建一个布局title.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/title_bg">

    <Button
        android: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" />

    <TextView
        android: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" />

    <Button
        android: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>
修改activity_main.xml,通过include语句将标题栏布局引入
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="wrap_content">
	<include layout="@layout/title"/>
</LinearLayout>

修改MainActivity,将系统自带的标题栏隐藏掉

//调用getSupportActionBar()方法来获得ActionBar实例
ActionBar actionbar = getSupportActionBar();
if(actionbar != null){
    //隐藏标题栏
	actionbar.hide();
}

创建自定义控件

引入的布局中有一些控件要求能够响应事件,但是如果在每个活动都注册一遍响应事件,就会增加重复代码,使用自定义控件可以解决该问题。

新建TitleLayout继承LinearLayout

public class TitleLayout extends LinearLayout {
    //重写构造函数
    public TitleLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        /*借助LayoutInflater来实现对标题栏动态加载。
         *通过LayoutInflater的from()方法构造LayoutInflater对象
         *调用inflate()方法动态加载布局文件,inflate()方法接受两个参数,第一个参数
         *是要加载的布局文件的id,这里我们传入R.layout.title,第二个参数是
         *给加载好的布局再添加一个父布局,这里我们想要指定TitleLayout,所以直接传入this
        */
        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 clicked Edit button",
                        Toast.LENGTH_SHORT).show();
            }
        });
    }
}

这就创建好了自定义控件,接下来我们需要在布局文件中添加这个自定义控件,修改activity_main.xml:

<com.example.uicustomviews.TitleLayout
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
/>

注意需要指明控件的完整类名

3.7编写界面的最佳实践

创建一个名为UIBestPractice项目

制作Nine-Patch图片

将图片设置为LinearLayout的背景图片,修改activity_main.xml中的代码

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

但是由于图片的宽度不足以填满整个屏幕的宽度,整张图片就被均匀的拉伸了,这时就需要Nine-Patch图片来进行改善。sdk的目录下有一个tools文件夹,在这个文件夹总中找到draw9patch.bat文件,我们就是用他来制作Nine-Patch图片。(不过要打开这个文件,必须要讲JDK的bin目录配置到环境变变量当中才行,比如你使用的是Android Studio内置的jdk,那么要配置的路径就是<Android Studio 安装路径>/jre/bin)

详情见原书P133-134

编写精美的聊天界面

由于要用到RecyclerView,因此需要添加依赖库

dependencies{
	implementation 'androidx.recyclerview:recyclerview:1.2.8'
}

编写主界面,修改activity_main.xml中的代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#d8e0e8" >
	
    <!--用于显示聊天的消息内容-->
    <androidx.recyclerview.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/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/send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Send" />

    </LinearLayout>

</LinearLayout>

定义消息的实体类,新建Msg

//然后定义一个消息的实体类
public class Msg {
    public static final int TYPE_RECEIVED = 0;
    public static final int TYPE_SEND = 1;
    private int type;    //消息的类别,0是接收,1是发送
    private String content;   //表示消息内容
    
    public Msg(String content ,int type) {
        this.content = content;
        this.type = type;
    }
    public String getContent() {
        return content;
    }
    public int getType() {
        return type;
    }
}

编写RecyclerView子项的布局,新建msg_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp">
    <LinearLayout
        android:id="@+id/left_layout"
        android:layout_width="match_parent"
        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>

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

创建RecyclerView的适配类,新建类MsgAdapter

public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.ViewHolder> {
    private List<Msg> mMsgList;

    static class ViewHolder extends RecyclerView.ViewHolder{
        LinearLayout leftLayout;
        LinearLayout rightLayout;
        TextView leftMsg;
        TextView 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);
        }
    }

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

    @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){
            holder.rightLayout.setVisibility(View.VISIBLE);
            holder.leftLayout.setVisibility(View.GONE);
            holder.rightMsg.setText(msg.getContent());
        }
    }

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

}

修改MainActivity中的代码,为RecyclerView初始化一些数据,并给发送按钮加入事件响应

public class MainActivity extends AppCompatActivity {

    private List<Msg> msgList = new ArrayList<>();
    private EditText inputText;
    private Button send;
    private RecyclerView msgRecyclerView;
    private MsgAdapter adapter;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initMsgs(); //初始化数据
        inputText = (EditText) findViewById(R.id.input_text);
        send = (Button) findViewById(R.id.send);
        msgRecyclerView = (RecyclerView) findViewById(R.id.msg_recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        msgRecyclerView.setLayoutManager(layoutManager);
        adapter = new MsgAdapter(msgList);
        msgRecyclerView.setAdapter(adapter);
        //发送按钮的点击事件,如果内容不为空则创建出一个新的Msg对象,并把它添加到msgList中
        //调用notifyItemInserted()方法通知列表有新的数据插入
        //接着调用RecyclerView的scrollToPosition()方法将显示的数据定位到最后一行
        //最后调用EditText的setText()方法将输入的内容清空
        send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String content = inputText.getText().toString();
                if(!"".equals(content)){
                    Msg msg = new Msg(content,Msg.TYPE_SENT);
                    msgList.add(msg);
                    adapter.notifyItemInserted(msgList.size()-1);
                    msgRecyclerView.scrollToPosition(msgList.size()-1);
                    inputText.setText("");
                }
            }
        });

    }

    private void initMsgs(){
        Msg msg1 = new Msg("Hello guy",Msg.TYPE_RECEIVED);
        msgList.add(msg1);
        Msg msg2 = new Msg("Hello.Who is that?",Msg.TYPE_SENT);
        msgList.add(msg2);
        Msg msg3 = new Msg("This is Ton. Nice to talking to you.",Msg.TYPE_RECEIVED);
        msgList.add(msg3);
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gavi曦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值