首页 > 代码库 > 第一行代码第二版(郭霖著)笔记之第三章(UI开发的点点滴滴)

第一行代码第二版(郭霖著)笔记之第三章(UI开发的点点滴滴)

1. 常用控件

  • Button
    当我们设置Button的内容为Hello的时候,系统会对Hello的所有英文字母进行大写转换,显示在屏幕上为HELLO,如果不想要这个效果,设置属性android:textAllCaps为false即可,代码如下:
<Button
android:textAllCaps="false"
android:text="Hello"
android:layout_width="match_parent"
android:layout_height="match_parent" />
  • EditText
    用android:maxLines属性来设置EditText的行数
  • ImageView
    1. 首先要明确图片不是放在mipmap里面的,而是放在有分辨率标识的drawable文件夹下的。
    2. 动态设置图片,代码为:
ivimageview.setImageResource(R.drawable.test);
  • ProgressBar
    ProgressBar用于在界面上显示一个进度条,表示我们的程序正在加载一些数据

1.利用控件自身的属性android:visibility来 进度条的显示消失,有三个值visible,invisible,gone,如:

android:visibility="gone"

2.利用代码控制进度条的显示消失,值分别为View.VISIBLE,View.GONE,View.INVISIBLE,代码如下:

progrebar.setVisibility(View.VISIBLE);
progrebar.setVisibility(View.GONE);
progrebar.setVisibility(View.INVISIBLE);**

3.如何获取ProgressBar的状态呢,代码如下:

progrebar.getVisibility()==View.GONE
progrebar.getVisibility()==View.INVISIBLE
progrebar.getVisibility()==View.VISIBLE

4.ProgressBar默认是圆形进度条,通过style属性可以修改它为水平进度条,代码如下:

<ProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:visibility="gone"
android:id="@+id/progre_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

5.当ProgressBar为水平进度条的时候,可以通过android:max设置进度条的最大进度百分比,还可以通过代码获取当前的进度以及动态修改进度。

<ProgressBar
android:id="@+id/progre_bar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="80" />
int progress = progrebar.getProgress();
progress = progress+10;
progrebar.setProgress(progress);
  • AlertDialog
    AlertDialog能够屏蔽其他控件的交互能力,置顶于所有界面元素之上,因此AlertDialog是用于提示一些非常重要的内容或者警告信息。比如为了防止用户误删重要的内容,在删除前弹出一个确认对话框。代码如下:
AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
dialog.setTitle("我是标题");
dialog.setMessage("我是内容");
dialog.setCancelable(false);
dialog.setPositiveButton("确定", new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialogInterface, int i) {
        Toast.makeText(MainActivity.this,"确定",Toast.LENGTH_SHORT).show();
    }
});
dialog.setNegativeButton("取消", new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialogInterface, int i) {
        Toast.makeText(MainActivity.this,"取消",Toast.LENGTH_SHORT).show();
    }
});
dialog.show();
  • ProgressDialog
    ProgressDialog和AlertDialog有点类似,都可以在界面弹出一个对话框,都能够屏蔽掉其他控件的交互能力。不同的是,ProgressDialog会在对话框中显示一个进度条,一般用于表示当前操作比较耗时,让用户耐心地等待。
ProgressDialog progressdialog = new ProgressDialog(MainActivity.this);
progressdialog.setTitle("This is ProgressDialog");
progressdialog.setMessage("Loading...");
progressdialog.setCancelable(true);  
progressdialog.show();

setCancelable的值为true表示可以点击对话框以外的屏幕区域和返回键实现关闭对话框的操作,false表示不可以,所以当传入false的时候,代码中一定要做好控制,当数据加载完成后必须要调用ProgressDialog的dismiss方法来关闭对话框,否则ProgressDialog将会一直存在。

2. 基本布局

线性布局

  • android:layout_gravity和android:gravity 区别
    1. 前者用于指定控件在布局中的对齐方式,它的对象是控件。
    2. 后者用于指定文字在控件中的对齐方式,它的对象是控件中的文字。
    3. 注意:在线性布局中,当排列方向是horizontal时,只有垂直方向的对齐方式才有效,因为此时水平方向上的长度是不固定的,每添加一个控件,水平方向上的长度都会改变。同样当排列方向是vertical时, 水平方向上的对齐方式才会生效。
  • android:layout_weight
    允许我们使用比例来指定控件的大小,如下代码,线性布局,排列方式:横向;TextView和Button 各占50%,代码如下:
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />

<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="点击" />
  • 假如我想让TextView占据Button之外的所有区域,如何设置呢?这个时候,我们设置Button的宽为wrap_content,利用android:layout_height = 1就可以实现,代码如下:
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />

<Button
android:textSize="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击点击" />

相对布局

  • 控件相对于父控件进行定位
    1. android:layout_alignParentRight 位于父控件的最右边
    2. android:layout_alignParentLeft 位于父控件的最左边
    3. android:layout_alignParentBottom 位于父控件的底部
    4. android:layout_alignParentTop 位于父控件的上部
    5. android:layout_centerHorizontal 位于父控件横向的中间
    6. android:layout_centerVertical 位于父控件纵向的中间
    7. android:layout_centerInParent 位于父控件横向和纵向的中间(中心)
  • 控件相对于控件进行定位
    1. android:layout_below 位于某控件的下面
    2. android:layout_above 位于某控件的上面
    3. layout_toLeftOf 位于某控件的左边
    4. android:layout_toRightOf 位于某控件的右边
  • 其他
    1. android:layout_alignLeft 表示一个控件的左边缘和另一个控件的左边缘对齐
    2. android:layout_alignRight 表示一个控件的右边缘和另一个控件的右边缘对齐
    3. android:layout_alignBottom 表示一个控件的底部和另一个控件的底部对齐
    4. android:layout_alignTop 表示一个控件的顶部和另一个控件的顶部对齐

百分比布局

你会发现只有线性布局支持android:layout_height属性,相对布局和帧布局都不支持。为了解决这个问题,Android引入了“百分比布局”。 在这种布局中,我们可以不再使用wrap_content、match_parent等方式来指定控件的大小,而是允许直接指定控件在布局中所占的百分比,这样的话就可以轻松实现平分布局甚至是任意比例分割布局的效果了,所以百分比布局为FrameLayout和RelativeLayout进行了功能扩展,提供了PercentFrameLayout和PercentRelativeLayout这两个全新的布局。以下是使用步骤:
- 添加依赖(百分比布局放在了support中,目的是兼容各个版本)

compile ‘com.android.support:percent:25.0.1‘ 
  • 代码如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.percent.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/bt_click"
    android:layout_gravity="left|top"
    android:text="点击点击"
    app:layout_heightPercent="50%"
    app:layout_widthPercent="50%" />
</android.support.percent.PercentFrameLayout>

注意:最外层使用PercentFrameLayout,由于百分比布局并不是内置在系统SDK当中的,所以需要把完整的包路径写出来。然后必须定义一个app的命名空间,这样才能使用百分比布局的自定义属性。在上面我们定义了一个Button,它的宽和高都是占据布局的50%,在这里我们可以使用app前缀的属性就是因为我们刚才定义了app的命名空间。
- PercentRelativeLayout与PercentFrameLayout相似,不再多说。

3. 自定义控件

所有的布局和控件都直接或者间接集成自View,当Android自带的控件并不能很好的满足我们的需求时,可以利用上面的继承机构来创建自定义控件。
- 引入布局
我们创建一个名为title的xml文件,当我们需要再次使用这个布局的时候,我们只用一行代码就可以实现,代码如下:

<include layout="@layout/title"></include>
  • 隐藏标题栏
ActionBar actionbar = getSupportActionBar();
if (actionbar!=null){
    actionbar.hide();
}
  • 创建自定义控件
    在这里,我们创建一个标题栏的自定义控件。有些童鞋说,引入布局不就可以了,干嘛非要自定义呢?之所以创建自定义控件,是因为引入布局的技巧确实解决了重复编写布局代码的问题,但是如果布局中有一些控件要求能够响应事件,我们还是需要在每一个活动中为这些控件单独编写一次事件注册的代码。比如标题栏中的返回按钮,其实不管是在哪一个活动中,这个按钮的功能都是相同的,即销毁当前活动。而如果在每一个活动中都需要重新注册一遍返回按钮的点击事件,无疑会增加很多重复的代码,这种情况最好是使用自定义控件的方式来解决。

1.新建TitleLayout继承自LinearLayout,代码如下

public class TitleLayout extends LinearLayout {
    public TitleLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.title,this);
    }
}

首先重写了LinearLayout中带有两个参数的构造函数,在布局中引入TitleLayout控件就会调用这个构造函数。  
然后在构造函数中需要对标题栏布局进行动态加载,这就要借助LayoutInflater来实现了。  
通过LayoutInflater的from方法可以构建出一个LayoutInflater对象,然后调用inflate方法就可以动态加载一个布局文件.  
inflate方法接收两个参数,第一个参数是要加载的布局文件的id,这里传入R.layout.title,  
第二个参数是给加载好的布局再添加一个父布局,这里我们想要指定为TitleLayout,于是直接传入this

2.自定义控件已经创建好了,然后我们需要在布局文件中添加这个自定义控件,修改activity_main.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">

    <com.beidou.guolin_3.TitleLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </com.beidou.guolin_3.TitleLayout>

</LinearLayout>

注意:添加自定义控件的时候,我们这里需要指明控件 完整类名,包名在这里是不可以省略的。

这篇文章主要介绍了ListView和RecyclerView的使用方法,虽然ListView已经过时了,但是如何使用我们还是应该了解的,毕竟RecyclerView是从ListView进化过来的,了解ListView的使用方法可以让我们对RecyclerView有更好的了解。

4. ListView

由于手机屏幕空间有限,能否一次性在屏幕上显示的内容并不多,当我们的程序中有大量的数据需要展示的时候,就可以借助ListView来实现。ListView允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据则会滚动出屏幕。
- 简单用法

1.创建ListView布局activity_main.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">

    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </ListView>

</LinearLayout>

2.准备数据

这些数据可以是从网上下载的,也可以是从数据库中读取的,应该视具体的应用程序场景而定。这里就简单使用了一个data数据来测试,里面包含了很多水果的名称。

private String[] data = {"Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape", 
"Pineapple", "Strawberry", "Cherry" , "Mango", "Apple",   
"Banana", "Orange", "Watermelon", "Pear", "Grape", 
"Pineapple", "Strawberry", "Cherry", "Mango","Apple",   
"Banana", "Orange", "Watermelon", "Pear", "Grape", "Pineapple", "Strawberry",   
"Cherry","Mango", "Apple", "Banana", "Orange", "Watermelon", "Pear",  
"Grape", "Pineapple", "Strawberry", "Cherry", "Mango"};

3.创建适配器

  • 数据中的数据是没有办法直接传递给ListView的,需要借助适配器来完成。Android中提供了很多适配器的实现类,其中郭霖认为最好用的就是ArrayAdapter,它可以通过泛型来指定要适配的数据类型,然后在构造函数中把要适配的数据传入。

  • ArrayAdapter有多个构造函数的重载,应该根据实际情况选择最合适的一种。这里由于我们提供的数据都是字符串,因此将ArrayAdaper的泛型指定为String,然后在ArrayAdatper的构造函数中依次传入当前的上下文、ListView子项布局的id,以及要适配的数据。

  • 注意:我们使用了android.R.layout.simple_list_item_1作为listview子项布局的id,这是一个Android内置的布局文件,里面只有一个ListView,可用于简单地显示一段文本。这样适配器就构建好了。

4.调用ListView的setAdapter方法,将构建的适配器对象传递进去,这样ListView和数据之间的关联就建立完成了。

  • 定制ListView的界面
    只能显示一段文本的TextView实在是太单调了,我们现在就来对ListView的界面进行定制,让它展示更多丰富的内容。

1.activity_main.xml代码不变,准备子项布局fruit_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="match_parent"
    android:orientation="horizontal">

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

2.准备数据

用initFruits()方法初始化所有的水果数据。在Fruit类的构造函数中将水果的名字和对应的图片id传入,然后把创建好的对象添加到水果集合列表中。这里我们用for循环将所有的水果数据添加了两遍,因为只添加一遍的话,数据量不足以充满整个屏幕。代码如下:

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

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 void setName(String name) {
        this.name = name;
    }

    public int getImageId() {
        return imageId;
    }

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

3.创建自定义的适配器

这个适配器继承自ArrayA ,并将泛型指定为Fruti类,新建类FruitAdapter,代码如下

public class FruitAdapter extends ArrayAdapter<Fruit> {

private int resourceId;

public FruitAdapter(Context context, int resource, List<Fruit> objects) {
    super(context, resource, objects);
    resourceId = resource;
}

@NonNull
@Override
public View getView(int position, View convertView, 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;
}
}

1. 这里FruitAdapter重写了父类的一组构造函数,用于将上下文、ListView子项布局的id和数据都传递进来。
2. 另外还又重写了getView()方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。
3. 1. 在getView()方法中,首先通过getItem()方法得到当前项的Fruit实例,
4. 然后使用LayoutInflater来为这个子项加载我们传入的子项布局。
注意:LayoutInflater的inflate方法接收三个参数,前两个参数在上面已经讲述了,第三个参数指定成
false,表示只让我们在父布局中声明的layout属性生效,但不为这个View添加父布局,因为一旦View有了父布
局之后,它就不能再添加到ListView中。
5.接着调用View的findViewbyid方法分别获取到ImageView和TextView的实例,并分别调用它们的
setImageResouce和setText方法来设置显示的图片和文字,最后将布局返回,
这样我们自定义的适配器就完成了。

4.在OnCreate方法中创建FruitAdapter对象,并将FruitAdapter作为适配器传递给ListView即可。

  • 提升ListView运行效率
    目前我们的ListView运行效率比较低,因为在FruitAdapter的getView()方法中,每次都将布局重新加载一遍,当ListView快速滚动的时候,这就会成为性能的瓶颈。我们在两个地方进行优化。

1.getView()方法中还有一个convertView参数,这个参数用于将之前加载的布局进行缓存,以便之后可以进行重用。修改FruitAdapter代码如下:

@NonNull
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    Fruit fruit = getItem(position);
    View view ;
    if (convertView == null){
        view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
    }else{
        view = convertView;
    }
    ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
    TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
    fruitImage.setImageResource(fruit.getImageId());
    fruitName.setText(fruit.getName());
    return view;
}

我们在getView方法中进行了判断,如果convertView为null,则使用LayoutInflater去加载布局,
如果不为null则直接对convertView进行重用。

2.getView方法,虽然不会重复去加载布局了,但是每次在getView()方法中还是会调用View的findViewById()方法来获取一次控件的实例。我们可以借助一个ViewHolder来对这部分性能进行优化,修改FruitAdapter中的代码,如下所示:

@NonNull
    @Override
    public View getView(int position, View convertView, 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
        }
        viewHolder.fruitImage.setImageResource(fruit.getImageId());
        viewHolder.fruitName.setText(fruit.getName());
        return view;
    }

    class ViewHolder{
        ImageView fruitImage;
        TextView fruitName;
    }

我们新增一个内部类,用于对控件的实例的缓存。当convertView为null的时候,创建一个ViewHolder对象,
并将控件的实例都存放在ViewHolder里,然后调用View的setTag()方法,将ViewHolder对象存储在View中
当convertView不为null的时候,则调用View的getTag()方法,把ViewHolder重新取出。
这样所有控件的实例都缓存在了ViewHolder里,就没有必要每次都通过findViewById方法来获取控件实例了。
  • ListView的点击事件
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Fruit fruit = fruitList.get(position);
        Toast.makeText(MainActivity.this,fruit.getName(),Toast.LENGTH_SHORT).show();
    }
});

我们使用setOnItemClickListener方法为ListView注册一个监听器,当用户点击了ListView中任何一个子项
时,就会回调onItemClick方法。在这个方法中可以通过postion参数判断用户点击的是哪一个子项,
然后获取到相应的水果,并通过Toast将水果的名字显示出来

5. RecyclerView

Android提供了一个更强大的滚动控件RecyclerView。它可以说是一个增强版的ListView,不仅可以轻松实现和ListView同样的效果,还可以优化ListView存在的各种不足的地方。官方更加推荐RecyclerView,因为它充分的解耦,不但可实现纵向滚动,还可以轻松实现横向滚动以及瀑布流的效果的实现。

  • RecyclerView的基本用法

1.添加依赖

compile ‘com.android.support:recyclerview-v7:25.0.1‘

2.修改activity_main.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.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v7.widget.RecyclerView>

</LinearLayout>

注意:由于RecyclerView并不是内置在系统SDK当中的,所以需要把完整的包路径写出来。

3.准备数据(水果图片和Fruit类和fruit_item.xml)

单独强调下fruit_item

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

    android:orientation="horizontal">

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

注意:最外层的布局是wrap_content,而不是match_parent,和ListView不相同。

4.自定义适配器

FruitAdapter类,让这个适配器继承自RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder.其中ViewHolder是我们在FruitAdapter中定义的一个内部类,代码如下所示:

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
    private List<Fruit> mFruitList;

    static class ViewHolder extends RecyclerView.ViewHolder{
        ImageView fruitImage;
        TextView fruitName;
        public ViewHolder(View view) {
            super(view);
            fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
            fruitName = (TextView) view.findViewById(R.id.fruit_name);
        }
    }

    public FruitAdapter(List<Fruit> fruitList){
        mFruitList = fruitList;
    }

    @Override
    public FruitAdapter.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;
    }

    @Override
    public void onBindViewHolder(FruitAdapter.ViewHolder holder, int position) {
        Fruit fruit = mFruitList.get(position);
        holder.fruitImage.setImageResource(fruit.getImageId());
        holder.fruitName.setText(fruit.getName());
    }

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

① 首先定义一个内部类ViewHolder,ViewHolder要继承自RecyclerView.ViewHolder。然后ViewHolder的构造函数中要传入一个View参数,这个View参数通常就是RecyClerView子项的最外城布局,那么我们就可以通过findViewById()方法来获取到布局中的ImageView和TextView的实例了。

② 接着,FruitAdapter中也有一个构造函数,这个方法用于把要展示的数据源传进来,并赋值给一个全局变量
mFruitList,我们后续的操作都将在这个数据源的基础上进行。

③ 然后,由于FruitAdapter是继承自RecyclerView.Adapter的,那么就必须重写onCreateViewHolder()、onBindViewHolder()和getItemCount()这3个方法。

④ onCreateViewHolder()方法是用于创建ViewHolder实例的,我们在这个方法中将fruit_item布局加载进来,然后创建一个ViewHolder实例,并把加载出来的布局传入到构造函数当中,最后将ViewHolder的实例返回。

⑤ onBindViewHolder()方法是用于对RecyclerView子项的数据进行赋值的,会在每个子项被滚动到屏幕内的时候执行,这里通过position参数得到当前项的Fruit实例,然后再将数据设置到ViewHolder的ImageView和TextView当中即可。

⑥ getItemCount()方法告诉RecyclerView一共有多少子项,直接返回数据源的 长度就可以了。

5.修改MainActivity中的代码,如下

@Override
    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.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
    }

* initFruits 初始化所有的水果数据

* onCreate 方法中我们先获取到RecyclerView的实例,然后创建了一个LinearLayoutManager对象,并将它设置到RecyclerView当中。LayoutManager用于指定RecyclerView的布局方式,这里使用的是LinearLayoutManager是线性布局的意思。

* 创建FruitAdapter实例,并将水果数据传入到FruitAdapter的构造函数中,最后调用RecyclerView的setAdapter()方法来完成适配器设置,这样RecyclerView和数据之间的关联就建立完成了。

  • 实现横向滚动
    ListView想要实现横向滚动就做不到了,而RecyclerView却轻松实现。

1.首先要对fruit_item 布局进行修改,如果要实现横向滚动的话,应该把fruit_item里的元素改成垂直排列才比较合理。代码如下:

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

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

** 我们将LinearLayout改成垂直方向排列,并把宽度设为100dp。这里 宽度指定为固定值是因为每种水果的文字长度不一致,如果用
wrap_content的话,RecyclerView的子项就会有长有短,非常不美观;而如果用match_parent的话,就会导致宽度过长,一个子项占满整个屏幕。
** 我们将Imageview和TextView都设置成在布局中水平居中,并且使用layout_marginTop属性让文字和图片之间保持一些距离。
2. 在原来MainActivity基础代码上修改布局的排列方向为横向排列

@Override
    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);
        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
    }

** MainActivity中加入了一行代码,调用LinearLayoutManger的setOrientation方法来设置布局的排列方向,默认是纵向排列的,我们传入LinearLayoutManager.HORIZONTAL表示让布局横向排列,这样RecyclerView就可以横向滚动了。
** ListView的布局排列是由自身管理,而RecyclerView则将这个工作交给了LayoutManager,LayoutManager中制定了一套可扩展的布局排列接口,子类只要按照接口的规范来实现,就能定制出各种不同排列方式的布局了。

  • 实现瀑布流布局

实现瀑布流布局只需修改布局的排列方式即可

1.修改MainActivity中的代码,只需一行代码即可实现效果

StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3,
StaggeredGridLayoutManager.VERTICAL);

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

2.修改下fruit_item的布局

<?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:layout_margin="5dp"
    android:orientation="vertical">

    <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的宽度由100dp改成了match_parent,因为瀑布流布局的宽度应该是根据布局的列数来自动适配的,而不是一个固定值。
** 使用layout_margin属性来让子项之间互留一点间距,这样不至于所有子项都紧贴。还有就是将TextView的对齐属性改成了居左对齐,因为待会我们会将文字的长度变长,如果还是居中显示就会感觉怪怪的了。
3.由于瀑布流的效果需要各个子项的高度不一致才能看出明显的效果,为此我们又使用了一个小技巧我们利用getRandomLengthName这个方法,这个方法使用Random 对象来创造一个1到20之间的随机数,然后将参数中传入的字符串重复随遍。代码如下:

public class MainActivity extends AppCompatActivity {

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruits();
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, 
        StaggeredGridLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
    }

    private void initFruits() {
        for (int i = 0; i < 5; 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);
            Fruit orange = new Fruit(getRandomLengthName("Orange"), 
            R.drawable.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new Fruit(getRandomLengthName("Watermelon"), 
            R.drawable.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit(getRandomLengthName("Pear"), 
            R.drawable.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit(getRandomLengthName("Grape"), 
            R.drawable.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit(getRandomLengthName("Pineapple"), 
            R.drawable.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit(getRandomLengthName("Strawberry"), 
            R.drawable.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit(getRandomLengthName("Cherry"), 
            R.drawable.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit(getRandomLengthName("Mango"), 
            R.drawable.mango_pic);
            fruitList.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();
    }
}
  • RecyclerView的点击事件

ListView看似在点击事件上比较人性化,其实并不如此。setOnItemClickListener方法注册的是子项的点击事件,但如果我想点击的是子项里面具体的某一个按钮,实现起来就比较麻烦了。为此RecyclerView摒弃了子项点击事件的监听器,所有的点击事件都由View去注册,就没有这个困扰了。

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
    private List<Fruit> mFruitList;

    static class ViewHolder extends RecyclerView.ViewHolder {
        View fruitView;
        ImageView fruitImage;
        TextView fruitName;

        public ViewHolder(View view) {
            super(view);
            fruitView = view;
            fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
            fruitName = (TextView) view.findViewById(R.id.fruit_name);
        }
    }

    public FruitAdapter(List<Fruit> fruitList) {
        mFruitList = fruitList;
    }

    @Override
    public FruitAdapter.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);
                Toast.makeText(view.getContext(), "you click view" + 
                fruit.getName(), Toast.LENGTH_SHORT).show();
            }
        });
        holder.fruitImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                int positon = holder.getAdapterPosition();
                Fruit fruit = mFruitList.get(positon);
                Toast.makeText(view.getContext(), "YOU CLICKED IMAGE" + 
                fruit.getName(), Toast.LENGTH_SHORT).show();
            }
        });
        return holder;
    }
    ...
}

** 我们先是修改了ViewHolder,在ViewHolder中添加了fruitView变量来保存子项最外层布局的实例,然后在onCreateViewHolder方法中注册点击事件就可以了。这里分别为最外层布局和ImageView都注册了点击事件,RecyClerView的强大之处也在这里,它可以轻松实现子项中任意控件或布局的点击事件。我们在两个点击事件中先获取了用户点击的position,然后通过position拿到相应的Fruit实例,再用Toast分别弹出两种不同的内容以示区别。

* 点击图片会触发ImageView的点击事件;点击文字部分,由于TextView并没有注册点击事件,因此点击文字这个事件会被子项的最外层布局捕获到。

6. 什么是Nine-Patch图片

它是一种被特殊处理过的png图片,能够指定哪些区域可以被拉伸、哪些区域不可以。如果聊天的信息背景图片不用Nine-Patch图片,那么当你一条信息量很大的时候,整个图片会被往长的拉伸往宽的拉伸。而我们需要的只是图片的某一些部分被拉伸。

  • 制作Nine-Patch图片

    1. 配置JDK的bin目录配置到环境变量当中。
    2. 在Android sdk目录下有一个tools文件夹,在这个文件夹中找到draw9patch.bat文件,我们就是使用它来制作Nine-Patch图片的。
    3. 双击draw9patch.bat文件,在导航栏点击File-Open 9-patch将你想制作成Nine-Patch的图片加载进来,图片哪里需要拉伸,就在图片的边缘拖动进行绘制即可,按住Shift键拖动可以进行擦除。
    4. 最后点击导航栏File-Save9-patch把绘制好的图片进行保存,此时文件名就是xxx.9.png图片。

7. 编写精美的聊天界面

需求:精美的聊天界面。
分析:上面的部分是RecyclerView,下面的部分是EditText+Button
方法:编写Recycler,处理Button点击事件,把信息放到Recyc中。

1.添加Recyc的依赖:

compile ‘com.android.support:recyclerview-v7:25.0.1‘

2.编写mainactivity.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.fkq.mytask.MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"></android.support.v7.widget.RecyclerView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <EditText
            android:id="@+id/et_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>

说明:上面是一个Recyc,下面是Edit+Button组合。

3.编写Recyc:

  • 新建消息的实体类Msg:
public class Msg {
    public static final int TYPE_RECEIVED = 0;
    public static final int TYPE_SENT = 1;
    private String content;
    private int type;

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

    public String getContent() {
        return content;
    }

    public int getType() {
        return type;
    }

}

说明:Msg类有两个字段,一个是内容,一个是内容的类型,其中内容的类型包括两类:TYPE_RECEIVED代表收到的信息;TYPE_SENT代表发出的信息。

4.新建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:orientation="vertical">

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

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

说明:让收到的信息居左对齐,让发出的信息居右对齐,并且分别使用message_left和message_right作为背景图。

5.新建适配器MsgAdapter:

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

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

    static class ViewHolder extends RecyclerView.ViewHolder {
        LinearLayout leftLayout;
        LinearLayout rightLayout;

        TextView leftMsg;
        TextView rightMsg;

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

    @Override
    public MsgAdapter.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(MsgAdapter.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();
    }
}

说明:在onBindViewHolder方法中增加了对消息类型的判断,如果这条信息是收到的,则显示左边的消息布局,如果这条消息是发出的,则显示右边的消息布局。

6.生成RecyclerView,处理Button的点击事件

public class MainActivity extends AppCompatActivity {
    private List<Msg> msgList = new ArrayList<>();
    private Button send;
    private RecyclerView msgRecyclerView;
    private MsgAdapter adapter;
    private EditText inputtext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initMsgs();
        inputtext = (EditText) findViewById(R.id.et_text);
        send = (Button) findViewById(R.id.send);
        msgRecyclerView = (RecyclerView) findViewById(R.id.rv);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        msgRecyclerView.setLayoutManager(layoutManager);
        adapter = new MsgAdapter(msgList);
        msgRecyclerView.setAdapter(adapter);
        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 baby", Msg.TYPE_RECEIVED);
        msgList.add(msg1);
        Msg msg2 = new Msg("Who are you?", Msg.TYPE_SENT);
        msgList.add(msg2);
        Msg msg3 = new Msg("This is Tom.", Msg.TYPE_RECEIVED);
        msgList.add(msg3);

    }
}

说明:
1. initMsgs方法初始化几条数据显示在RecyclerView中显示。
2. 点击Buttton按钮获取EditText内容,如果内容不为null,则创建出一个新的Msg对象,并把它添加到msgList列表中去。
3. 调用了适配器的notifyItemInserted方法,用于通知列表有新的数据插入,这样新增的一条信息才能够在RecyclerView中显示。
4. 调用Recyc的scrollToPosition将显示的数据定位到最后一行,以保证一定可以看到最后发出的一条信息。
5. 调用setText方法将输入的内容清空。

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    第一行代码第二版(郭霖著)笔记之第三章(UI开发的点点滴滴)