Android学习笔记(3)——UI开发的点点滴滴

第三章 UI开发的点点滴滴

      • 3.1 常用控件的使用方法
        • 3.1.1 TextView
        • 3.1.2 Button
        • 3.1.3 EditText
        • 3.1.4 ImageView
        • 3.1.5 ProgressBar
        • 3.1.6 AlertDialog
        • 3.1.7 ProgressDialog
      • 3.2 详解四种布局
        • 3.2.1 线性布局
        • 3.2.2 相对布局
        • 3.2.3 帧布局
        • 3.2.4 百分比布局(被弃用,改用约束布局)
      • 3.3 创建自定义布局
        • 3.3.1 引入布局
        • 3.3.2 自定义控件
      • 3.4 最常用和最难用的控件——ListView
        • 3.4.1 ListView的简单用法
        • 3.4.2 定制ListView的界面
        • 3.4.3 提升KListView的运行效率
        • 3.4.4 ListView的点击事件
      • 3.5 更强大的滚动控件——RecyclerView
        • 3.5.1 RecyclerView的基本用法
        • 3.5.2 实现横向滚动和瀑布流布局
        • 3.5.3 RecyclerView的点击事件
      • 3.6 编写界面的最佳实践
        • 3.6.1 制作Nine-Patch图片
        • 3.6.2 编写精美的聊天界面

3.1 常用控件的使用方法

3.1.1 TextView
  1. 概述:TextView可以说是Android中最简单的一一个控件了,你在前面其实已经和它打过一些交道了。
    它主要用于在界面上显示一段文本信息。

  2. 控件内容:

    1. android : id 给当前控件定义唯一标识符

    2. android : layout_width 指定控件的宽度

    3. android : layout_height 指定控件的高度

      补充:2.3两个属性所有Android控件都有,可选值有三种:match_ parent(和父布局一样)、 fill_ parent和Wrap_ content (包裹内容)。

    4. android : text 指定TextView中显示的文本内容

    5. android : gravity 指定文字对齐方式,可选值:top、bottom(底部)、left
      、 right、center等,可以用“|”来同时指定多个值

    6. android : textSize 指定文字的大小,字体大小单位为sp

    7. android : textColor 指定文字的颜色

3.1.2 Button
  1. 概述:Button是一个用户界面对象,在单击时向目标发送操作消息。

  2. 控件内容:

    1. android : text 指定按钮中显示的文本内容
    2. android : id 给当前控件定义唯一标识符
    3. android : textAllCaps 是否对按钮显示的文本内容进行大写转换
  3. 实现View.OnClickListener类,重写onClick(View v)方法;Button对象调用setOnClickListener(this)方法时直接传活动本身

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

  2. 控件内容:

    1. android : hint 指定一段提示性的文本
    2. Android : maxLines 指定EditText最大行数
  3. 操作:

    1. EditText对象.getText().toString()将所输入文本转换为字符串
3.1.4 ImageView
1. 概述:ImageView是用于在界面上展示图片的一个控件,它可以让我们的程序界面变得更加丰富多彩。
2. 控件内容:
   1. android : src     给ImageView指定了一张图片

3. 操作:
   1. imageView.setImageResource(参数)     更改布局中所指定的图片或添加一张照片在此ImageView布局中,参数为照片的路径(通过引用传递)
3.1.5 ProgressBar
  1. 概述:ProgressBar用于在界面上显示一个进度条,表示我们的程序正在加载一些数据。

  2. 控件内容:

    1. android : visibility 所有Android控件都具有这个属性,表示控件的可见性

      可选值:visible表示控件时可见的,这个值时默认值,不指定visible时,控件都是可见的, invisible表示控件不可见,但他仍然占据着原来的位置和大小 , gone表示控件不仅不可见,而且不再占用任何屏幕空间

    2. android : max 给进度条设置一个最大值,然后在代码中动态的更改进度条的进度

    3. style="?android:attr/progressBarStyleHorizontal" 指定进度条为条形进度条

  3. 操作:

    1. ProgressBar对象.setVisiviblity(参数) 更改布局中该控件的可见性属性
    2. ProhressBar对象.setProgress(参数) 更改布局中进度跳的大小,初始化为0
    3. ProgressBar对象.getProgress() 获取当前进度条的进度
3.1.6 AlertDialog
  1. 概述:AlertDialog可以在当前的界面弹出一个对话框,这个对话框是置顶于所有界面元素之上的,
    能够屏蔽掉其他控件的交互能力,因此AlertDialog 一般都是**用于提示一些非常重要的内容或者警告信息。**比如为了防止用户误删重要内容,在删除前弹出一个确认对话框。

  2. 操作代码:

     AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
                dialog.setTitle("This is a Dialog");
                dialog.setCancelable(false);
                dialog.setMessage("something important");
                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();
    
3.1.7 ProgressDialog
  1. 概述:ProgressDialog和AlertDialog有点类似,都可以在界面上弹出一个对话框,都能够屏蔽掉其
    他控件的交互能力。不同的是,ProgressDialog 会在对话框中显示一个进度条,一般用于表示当
    前操作比较耗时,让用户耐心地等待。

  2. 操作代码:

        ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
        progressDialog.setTitle("This is a ProgressDialog");
        progressDialog.setMessage("Loading……");
        progressDialog.setCancelable(true); //设置是否可以通过返回键退出此布布局
        progressDialog.show();
    
  3. 补充:注意,如果在setCancelable()中传人了false, 表示ProgressDialog 是不能通过Back键
    取消掉的,这时你就一定要在代码中做好控制,当数据加载完成后必须要调用ProgressDialog 的
    dismiss()方法来关闭对话框,否则ProgressDialog 将会一直存在。

3.2 详解四种布局

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X1cGf4K8-1627825381155)(C:\Users\过客\AppData\Roaming\Typora\typora-user-images\image-20210723094201415.png)]

3.2.1 线性布局
  1. 概述:LinearLayout又称作线性布局,是一种非常常用的布局。正如它的名字所描述的一样,这个布局会将它所包含的控件在线性方向上依次排列。相信你之前也已经注意到了,我们在上一节中
    学习控件用法时,所有的控件就都是放在LinearLayout布局里的,因此上一节中的控件也确实是
    在垂直方向.上线性排列的。

  2. 注意:

    1. 这里需要注意,如果LinearLayout 的排列方向是horizontal, 内部的控件就绝对不能将宽度指定为match_ parent, 因为这样的话,单独一个控件就会将整个水平方向占满,其他的控件就
      没有可放置的位置了。同样的道理,如果LinearLayout的排列方向是vertical,内部的控件就不能
      将高度指定为match _parent。


    2. LinearL ayout的排列方向是horizontal时,只有垂直方向上的对齐方式才会生效,因为此时水平方向上的长度是不固定的,每添加一个控件,水平方向上的长度都会改变,因而无法指定该方向上
      的对齐方式。同样的道理,当LinearLayout的排列方向是vertical 时,只有水平方向上的对齐方式才会生效

  3. 布局内容:

    1. android : weigth 指定控件在布局中所占的比例
    2. android : layout_gravity 指定布局的对其方式
3.2.2 相对布局
  1. 概述:RelativeLayout又称作相对布局,也是一种非常常用的布局。和LinearLayout 的排列规则不同,RelativeLayout 显得更加随意一些, 它可以通过相对定位的方式让控件出现在布局的任何位置。也正因为如此,RelativeLayout 中的属性非常多,不过这些属性都是有规律可循的,其实并不难理解和记忆。
  2. 理解:两种相对,一种相对父布局,一种相对控件
3.2.3 帧布局
  1. 概述:FrameLayout又称作帧布局,它相比于前面两种布局就简单太多了,因此它的应用场景也少了很多。这种布局没有方便的定位方式,所有的控件都会默认摆放在布局的左上角
3.2.4 百分比布局(被弃用,改用约束布局)
  1. 概述:ConstraintLayout

3.3 创建自定义布局

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-na1qSBXj-1627825381158)(C:\Users\过客\AppData\Roaming\Typora\typora-user-images\image-20210723145556015.png)]

  1. 概述:可以看到,我们所用的所有控件都是直接或间接继承自View 的,所用的所有布局都是直接
    或间接继承自ViewGroup 的。View 是Android中最基本的一种UI组件,它可以在屏幕上绘制一
    块矩形区域,并能响应这块区域的各种事件,因此,我们使用的各种控件其实就是在View的基
    础之上又添加了各自特有的功能。而ViewGroup则是一种特殊的View,它可以包含很多子View
    和子ViewGroup,是-一个用于放置控件和布局的容器。
3.3.1 引入布局
  1. 概述:一般我们的程序中可能有很多个活动都需要这样的标题栏,如果在每个活动的布局中都编
    写一遍同样的标题栏代码,明显就会导致代码的大量重复。这个时候我们就可以使用引入布局的
    方式来解决这个问题,新建一个布 局title.xml,代码如下所示:

  2. 优点:减少重复代码的编写,解决重复编写布局代码的问题

  3. 布局内容:

    1. android : layout_margin 指定控件在上下左右偏移的距离(四个方向同时偏移)
    2. android : layout_marginTop 指定控件与上偏移的距离
  4. 将布局引入:

        <include layout = "@layout/title"/>
    
  5. 将原有标题隐藏方法

    setContentView(R.layout.activity_main);        ActionBar actionbar = getSupportActionBar();        if(actionbar != null) {            actionbar.hide();        }
    
3.3.2 自定义控件
  1. 实现思路:

    1. 新建一个类将含有控件加载进此类中,此类继承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. 在布局文件中添加这个自定义控件

      实现代码:

      <com.example.uiwidgettest.TitleLayout
              android:layout_height="wrap_content"
              android:layout_width="match_parent"
              />
      
    3. 给控件注册点击事件

      代码实现:

       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 view) {
                          ((Activity) getContext()).finish();
                      }
                  });
                  titleEdit.setOnClickListener(new OnClickListener() {
                      @Override
                      public void onClick(View view) {
                          Toast.makeText(getContext(),"You clicked Edit button",
                                  Toast.LENGTH_SHORT).show();
                      }
                  });
      

3.4 最常用和最难用的控件——ListView

  1. 概述:ListView绝对可以称得上是Android中最常用的控件之一,几乎所有的应用程序都会用到它。
    由于手机屏幕空间都比较有限,能够一次性在屏幕上显示的内容并不多,当我们的程序中有大量
    的数据需要展示的时候,就可以借助ListView 来实现。ListView 允许用户通过手指上下滑动的方
    式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据则会滚动出屏幕。
3.4.1 ListView的简单用法
  1. 使用步骤:

    1. 先在布局中注册一个ListView控件

      代码如下:

      <ListView        android:id="@+id/List_view"        android:layout_width="match_parent"        android:layout_height="match_parent" />
      
    2. 创建一个适配器,因为数组中的内容无法传递到ListVIew中,ArrayAdapter(适配器类)

      代码如下:

              ArrayAdapter<String> adapter = new ArrayAdapter<>			(MainActivity.this,android.R.layout.simple_list_item_1, data);
      
    3. 通过适配器将数据传递给ListView对象

      代码如下:

      		ListView listView = (ListView) findViewById(R.id.List_view);        listView.setAdapter(adapter);
      
3.4.2 定制ListView的界面
  1. 使用步骤:

    1. 自定义一个类用于存放ListView中个控件得Id或文本内容

      package com.example.listview2;public class Fruit {    private String name;	//TextView显示的内容    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;    }}
      
    2. 编写一个显示自定义内容的适配器(没看懂),自定义适配器继承ArrayAdapter<>

      package com.example.listview2;
      
      import android.content.Context;
      import android.view.LayoutInflater;
      import android.view.View;
      import android.view.ViewGroup;
      import android.widget.ArrayAdapter;
      import android.widget.ImageView;
      import android.widget.TextView;
      import androidx.annotation.NonNull;
      import androidx.annotation.Nullable;
      import java.util.List;
      
      public class FruitAdapter extends ArrayAdapter<Fruit> {
          private int resourceId;
      
          public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects) {	//编写自定义适配器的构造器
              super(context,textViewResourceId,objects);	//是由父类的构造器,父类为ArrayAdapter有三个参数
              resourceId = textViewResourceId;	//获取自定义布局的引用值(引用值好像一般编译器会自动分配一个int值,以此值匹配引用值)
          }
      
          @NonNull
          @Override
          public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
              Fruit fruit = getItem(position);	//获得此位置的Fruit对象,理解:ListView会将所传入数据进行排列,为其分配对性的position值,当滑动界面时,每次都会调用getView方法刷新界面,新的position进入界面,获取此位置Fruit对象,为此加载对应布局,以实现布局的滚动
              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());	//通过Fruit对象中存储的信息对控件进行设置
              fruitName.setText(fruit.getName());
              return view;
          }
      }
      
      
    3. 在主活动中完成内容的初始化和通过自定义适配器实现自定义ListView的显示

      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);
          }
      
3.4.3 提升KListView的运行效率
  1. 目前我们ListView的运行效率是很低的,因为在FruitAdapter的getView()方法中,每次都将布局重新加载了一遍,当ListView快速滚动的时候,这就会成为性能的瓶颈。
    仔细观察会发现,getView()方法中还有一个convertView 参数,这个参数用于将之前加
    载好的布局进行缓存,以便之后可以进行重用

  2. 代码实现:

    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {	//每加载一条信息执行一次此方法
            Fruit fruit = getItem(position);
            View view;
            ViewHolder viewHolder;
            if(convertView == null) {
                view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
                viewHolder = new ViewHolder();
                viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
                viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name);
                view.setTag(viewHolder);	//将viewHolder存储到view中
            }else {
                view = convertView;
                viewHolder = (ViewHolder) view.getTag();	//将view中的viewHolder拿出
            }
    
            viewHolder.fruitImage.setImageResource(fruit.getImageId());
            viewHolder.fruitName.setText(fruit.getName());
            return view;
        }
    
        class ViewHolder {	//用于存储布局信息
            ImageView fruitImage;
            TextView fruitName;
        }
    
3.4.4 ListView的点击事件
  1. 通过调用setOnItemClickListener()方法为ListView注册了一个监听器,用户点击了ListView 中的任何一个子项时,就会回调onItemClick()方法。在这个方法中可以通过position参数判断出用户点击的是哪一个子项。

  2. 实现代码:(在主活动中)

    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            Fruit fruit = fruitList.get(position);
            Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show();
        }
    });
    

3.5 更强大的滚动控件——RecyclerView

3.5.1 RecyclerView的基本用法
  1. 实现步骤:

    1. 添加依赖:打开app/build.gradle文件,在dependencies中添加

       implementation 'androidx.recyclerview:recyclerview:1.2.1'
      
    2. 在布局中添加RecyclerView控件,添加 代码如下:

      <androidx.recyclerview.widget.RecyclerView		
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:id="@+id/recycler_view"
              />
      
    3. 为RecyclerView准备一个适配器:

      package com.example.recyclerview;
      
      import android.view.LayoutInflater;
      import android.view.View;
      import android.view.ViewGroup;
      import android.widget.ImageView;
      import android.widget.LinearLayout;
      import android.widget.TextView;
      
      import androidx.annotation.NonNull;
      import androidx.recyclerview.widget.RecyclerView;
      
      import java.util.List;
      
      public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
          private List<Fruit> mFruitList;
      
          static class ViewHolder extends RecyclerView.ViewHolder {	//传入View参数,这个参数通常就是RecyclerView子项的最外层布局了,可通过findViewById()方法获得控件实例(此例为ImageView和TextView)
              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;
          }
      
          //由于继承了RecycleView.Adapter,所以需要重写下面三个方法
          @Override
          //创建ViewHolder
          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;
          }
      
          @Override
          public void onBindViewHolder(ViewHolder holder, int position) {	//用于对RecyclerView子项的数据进行赋值,会在每项子项被滚动到屏幕内的时候被执行,通过position参数得到当前项的Fruit实例,然后再将数据设置到ViewHolder的ImageView和TextView当中即可。
      
              Fruit fruit = mFruitList.get(position);
              holder.fruitImage.setImageResource(fruit.getImageId());
              holder.fruitName.setText(fruit.getName());
          }
      
          @Override
          public int getItemCount() {	//告诉RecyclerView一共有多少子项
              return mFruitList.size();
          }
      }
      
      
    4. 在主活动中设置RecycolerView实例:

          RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
      
          LinearLayoutManager layoutManager = new LinearLayoutManager(this);	//用于指定RecyclerView的布局方式
          recyclerView.setLayoutManager(layoutManager);
          FruitAdapter adapter = new FruitAdapter(fruitList);
          recyclerView.setAdapter(adapter);
      
3.5.2 实现横向滚动和瀑布流布局
  1. 横向滚动实现步骤:(在上一代码基础上)

    1. 设置子布局的布局格式:

      <?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" />	//与上一控件间隔10dp
      </LinearLayout>
      
    2. 在主活动中设置RecyclerView的排列格式:

      		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);
      
  2. 瀑布流布局实现步骤:

    1. 设置子布局的布局格式:

      <?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>
      
    2. 在主活动中设置RecyclerView的排列格式:

      `@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);	//设置瀑布流代码,第一个参数为指定布局列数,第二个参数指定布局的排列方向
      
              //LinearLayoutManager layoutManager = new LinearLayoutManager(this);
              //layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
      
              recyclerView.setLayoutManager(layoutManager);
              FruitAdapter adapter = new FruitAdapter(fruitList);
              recyclerView.setAdapter(adapter);
          }
      
3.5.3 RecyclerView的点击事件
  1. 概述:不同于ListView的是, RecyclerView并没有提供类似于setOnItemClickListener()
    这样的注册监听器方法,而是需要我们自己给**子项具体的View(注册给子项布局)**去注册点击事件,相比于ListView
    来说,实现起来要复杂一些。

  2. 实现代码:(在适配器中)

    package com.example.recyclerview;
    
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ImageView;
    import android.widget.LinearLayout;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import androidx.annotation.NonNull;
    import androidx.recyclerview.widget.RecyclerView;
    
    import java.util.List;
    
    public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
        private List<Fruit> mFruitList;
    
        static class ViewHolder extends RecyclerView.ViewHolder {
            View fruitView;		//定义去哪句的一个View对像
            ImageView fruitImage;
            TextView fruitName;
    
            public ViewHolder(View view) {
                super(view);
    
                fruitView = view;	//获得子项View对象
                fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
                fruitName = (TextView) view.findViewById(R.id.fruit_name);
            }
        }
    
        public FruitAdapter(List<Fruit> fruitList) {
            mFruitList = fruitList;
        }
    
        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
            final ViewHolder holder = new ViewHolder(view);
            //为子项布局的设置点击事件
            holder.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 clicked view" + fruit.getName(), Toast.LENGTH_SHORT).show();
                }
            });
            holder.fruitImage.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    int position = holder.getAdapterPosition();
                    Fruit fruit = mFruitList.get(position);
                    Toast.makeText(view.getContext(), "you clicked image" + fruit.getName(), Toast.LENGTH_SHORT).show();
                }
            });
            return holder;
        }
    
        @Override
        public void onBindViewHolder(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();
        }
    }
    
    

3.6 编写界面的最佳实践

3.6.1 制作Nine-Patch图片
  1. 先添加一张图片,然后在导航栏点击File →Open 9-patch ,将message_ left.png 加载进来,如图所示:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cIJCfdGk-1627825381160)(C:\Users\过客\AppData\Roaming\Typora\typora-user-images\image-20210729091632381.png)]

使用步骤:通过点击边框设置宽和高不够时被拉伸的部分,按住Shift键拖动可以尽心那个擦除

3.6.2 编写精美的聊天界面
  1. 实现步骤:

    1. 通过RecyclerView用于显示聊天的消息内容

      RecyclerView的子项布局代码:(显示接收到的消息和发送的消息)

      <?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"
          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>
      
          <LinearLayout
              android:id="@+id/right_layout"
              android:background="@drawable/message_right"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_gravity="right"
              android:layout_margin="10dp">
      
              <TextView
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:id="@+id/right_msg"
                  android:layout_gravity="center"
                  android:layout_margin="10dp"/>
          </LinearLayout>
      </LinearLayout>
      
    2. 定义消息的实体类:

      package com.example.uibestpractice;
      
      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;
          }
      }
      
      
    3. 创建RecyclerView的适配器,新建类MsgAdapator

      package com.example.uibestpractice;
      
      import android.view.LayoutInflater;
      import android.view.View;
      import android.view.ViewGroup;
      import android.widget.LinearLayout;
      import android.widget.TextView;
      
      import androidx.annotation.NonNull;
      import androidx.recyclerview.widget.RecyclerView;
      
      import java.util.List;
      
      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());	//用右布局的TextView显示数据
              }else if(msg.getType() == Msg.TYPE_SENT) {	//输入信息显示右布局,隐藏左布局
                  holder.leftLayout.setVisibility(View.GONE);	
                  holder.rightLayout.setVisibility(View.VISIBLE);
                  holder.rightMsg.setText(msg.getContent());	//用左布局的TextView显示数据
              }
          }
      
          @Override
          public int getItemCount() {
              return mMsgList.size();
          }
      }
      
      
    4. 修改MainActivity中的代码,来为RecyclerView初始化一些数据, 并给发送按钮加入事件响应,代码如下所示:

      package com.example.uibestpractice;
      
      import androidx.appcompat.app.AppCompatActivity;
      import androidx.recyclerview.widget.LinearLayoutManager;
      import androidx.recyclerview.widget.RecyclerView;
      
      import android.os.Bundle;
      import android.view.View;
      import android.widget.Button;
      import android.widget.EditText;
      
      import java.util.ArrayList;
      import java.util.List;
      
      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();	//初始化RecyclerView数据
              send = (Button) findViewById(R.id.send);	//加载按键控件
              msgRecyclerView = (RecyclerView) findViewById(R.id.msg_recycler_view);	//加载RecyclerView控件
              inputText = (EditText) findViewById(R.id.input_text);	//加载EditText控件
              LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);	用于指定RecyclerView的布局方式
              msgRecyclerView.setLayoutManager(linearLayoutManager);
              adapter = new MsgAdapter(msgList);	//创建适配器,适配数据
              msgRecyclerView.setAdapter(adapter);	//将数据传入RecyclerView中
              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);	//将信息添加进list集合,用于在RecyclerView加载
                          adapter.notifyItemInserted(msgList.size() - 1);	//有新消息时刷新RecyclerView的显示
                          msgRecyclerView.scrollToPosition(msgList.size() - 1);	//将RecyclerView定位到最后一行
                          inputText.setText("");
                      }
                  }
              });
          }
      
          private void initMsgs() {	//初始化RecyclerView的数据
              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 Tom. Nice talking to you", Msg.TYPE_RECEIVED);
              msgList.add(msg3);
          }
      }
      

你可能感兴趣的:(android)