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

第三章——UI开发

1、如何编写程序界面

程序界面编写有很多种方法,主要分为可视化编辑器和XML代码编写,可视化编辑器虽然方便,但是不利于我们真正了解界面后面的实现原理,XML编写一些比较复杂的界面比较方便。

2、常见控件的使用方法

2.1、TextView

主要用于在界面上显示一段文本信息

<?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">
    
    <TextView
        android:id="@+id/text_view"  //给当前控件定义了唯一标识符
        android:layout_width="match_parent" //指定了控件的宽度
        android:layout_height="wrap_content" //指定了控件的高度
        android:text="This is a TestView!" //指定TextView显示的文本内容
/>

</LinearLayout>

android:layout_width和android:layout_height指定了控件的高度和宽度,所有的控件都有这两个属性,可选值有3个:match_parent、fill_parent和wrap_content。match_parent、fill_parent都表示让当前控件的大小和父布局的大小一样,也就是由父布局来决定当前控件的大小,而wrap_content表示让当前控件的大小能够刚好包含住里面的内容,也就是由控件内容决定当前控件的大小。上面的代码就表示让TextView的宽度和父布局一样宽,也就是手机屏幕的宽度,让TextView的高度足够包含住里面的内容就行。

TextView的文字默认是左上角对齐的,由于文字内容长度不够,我们无法看出TextView的宽度,现在我们修改一下对齐方式

<?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">
    <TextView
        android:id="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="This is a TestView!"
        android:gravity="center"
/>

</LinearLayout>

我们通过android:gravity指定文字的对齐方式,可选值有top、bottom、left、right、center等,可以用|同时指定多个值。center等同于center_vertical|center_horizontal,表示文字在垂直和水平方向都居中对齐。

我们还可以对文字的大小以及颜色进行修改

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

    <TextView
        android:id="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="This is a TestView!"
        android:gravity="center"
        android:textSize="24sp"
        android:textColor="#00ff00"
/>

</LinearLayout>

2.2、Button

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

    <TextView
        android:id="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="This is a TestView!"
        android:gravity="center"
        android:textSize="24sp"
        android:textColor="#00ff00"
/>
     <Button
         android:id="@+id/button"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:text="Button"/>
</LinearLayout>

![image-20230406130337767](/Users/whitebird/Library/Application Support/typora-user-images/image-20230406130337767.png)

你可能会发现一个问题,我们text里是Button,但是界面上却是大写字母,这是由于系统会对Button中所有的英文字母自动进行大写转换,不过我们要是不想要,可以进行相关配置关闭。

android:textAllCaps="false"

通过匿名类注册监听器

package com.example.uiwidgettest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button1=(Button)findViewById(R.id.button);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                
            }
        });
}

通过接口注册监听器

package com.example.uiwidgettest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

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.button);
        button1.setOnClickListener(this);
}
    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.button:
                break;                
            default:               
        }
    }
}

2.3、EditText

EditText允许用户在控件里输入和编辑内容。

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

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/edit_text"/>
</LinearLayout>

其实到这里我们可以总结出Android控件的使用规律了,用法基本上很相似,给控件定义一个id,再指定控件的高度和宽度,然后再适当加入一些控件特有的属性就差不多了。

通过android:hint=”Please input something:”可以给输入框增加一些提示

由于我们的高度设置为wrap_content,当我们输入足够多的字符时,会不断往下拉伸,可以通过android:maxLines=”2”,限制输入框最多显示2行,当输入内容超过2行,文本就会向上滚动。通过Button配合EditText打印出输入框内的内容

package com.example.uiwidgettest;

import android.support.v7.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 implements View.OnClickListener {

    private EditText editText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button1=(Button)findViewById(R.id.button);
         editText=(EditText) findViewById(R.id.edit_text);
        button1.setOnClickListener(this);
}


    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.button:
                String inputText=editText.getText().toString();
                Toast.makeText(this, inputText, Toast.LENGTH_SHORT).show();
                break;
            default:
        }
    }
}

2.4、ImageView

用于在界面上显示图片的一个控件,图片一般存放在drawable开头的目录下

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/image_view"
    android:src="@drawable/image1"/>

当我们点击button后切换图片

package com.example.uiwidgettest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText editText;
    private ImageView imageView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button1=(Button)findViewById(R.id.button);
         editText=(EditText) findViewById(R.id.edit_text);
         imageView=(ImageView)findViewById(R.id.image_view);
        button1.setOnClickListener(this);
}
    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.button:
                String inputText=editText.getText().toString();
                Toast.makeText(this, inputText, Toast.LENGTH_SHORT).show();
                imageView.setImageResource(R.drawable.image2);
                break;
            default:

        }
    }
}

点击Button后

![image-20230406165135007](/Users/whitebird/Library/Application Support/typora-user-images/image-20230406165135007.png)

2.5、ProgressBar

用于在界面上显示一个进度条,表示我们的程序正在加载一些数据。

<ProgressBar
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:id="@+id/process_bar"/>

这个加载进度条会一直都在,但是我们的数据总有加载完的时候,怎样才能让进度条在数据加载完成时消失?这个时候就需要一个新的知识点。Android控件的可见属性。所有的安卓控件都具有这个属性,我们可以通过android:visibility=””进行设置,可选值有:visible、invisible、和gone。visible表示控件可见,这个是不对android:visibility进行设置时的默认值,invisible表示控件不可见,但是它仍然占据着原来的位置和大小,可以理解变成了透明状态了。gone则表示控件不仅不可见,而且不再占用任何屏幕空间。另外我们也可以通过代码来设置控件的可见性,progressBar.setVisibility(),传入值为View.VISIBLE、View.INVISIBLE、

View.GONE

package com.example.uiwidgettest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText editText;
    private ImageView imageView;
    private ProgressBar progressBar;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button1=(Button)findViewById(R.id.button);
         editText=(EditText) findViewById(R.id.edit_text);
         imageView=(ImageView)findViewById(R.id.image_view);
         progressBar=(ProgressBar)findViewById(R.id.process_bar) ;
        button1.setOnClickListener(this);

}

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.button:
                String inputText=editText.getText().toString();
                Toast.makeText(this, inputText, Toast.LENGTH_SHORT).show();
                imageView.setImageResource(R.drawable.image2);
                if (progressBar.getVisibility()==View.GONE){
                    progressBar.setVisibility(View.VISIBLE);
                }else {
                    progressBar.setVisibility(View.GONE);
                }
                break;
            default:

        }
    }
}

另外我们可以指定ProcessBar的样式,刚才是圆形进度条,通过style属性可以设置成水平进度条。

<ProgressBar
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/process_bar"
    android:visibility="visible"
    style="?android:attr/progressBarStyleHorizontal"
    android:max="100"
    />

![image-20230406171435910](/Users/whitebird/Library/Application Support/typora-user-images/image-20230406171435910.png)

@Override
public void onClick(View view) {
    switch (view.getId()){
        case R.id.button:
            String inputText=editText.getText().toString();
            Toast.makeText(this, inputText, Toast.LENGTH_SHORT).show();
            imageView.setImageResource(R.drawable.image2);
            int progress=progressBar.getProgress();
            progress=progress+10;
            progressBar.setProgress(progress);
            break;
        default:

    }
}

每次点击Button,进度条+10

![image-20230406171702278](/Users/whitebird/Library/Application Support/typora-user-images/image-20230406171702278.png)

2.6、AlertDialog

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

AlertDialog.Builder dialog=new AlertDialog.Builder(MainActivity.this);
               dialog.setTitle("Dialog");//设置弹框标题
               dialog.setMessage("Something important");//设置弹框内容
               dialog.setCancelable(false);//能否用back键关闭对话框
               dialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {//为对话框设置确认按钮的点击事件
                   @Override
                   public void onClick(DialogInterface dialogInterface, int i) {

                   }
               });
               dialog.setNegativeButton("Cancle", new DialogInterface.OnClickListener() {//为对话框设置取消按钮的点击事件
                   @Override
                   public void onClick(DialogInterface dialogInterface, int i) {

                   }
               });
               dialog.show();

而且back键无法使用

2.7、ProgressDialog

ProgressDialog会在对话框中显示一个进度条,一般用于表示当前的操作比较耗时,让用户耐心地等待。它的用法和AlertDialog也比较相似。

@Override
public void onClick(View view) {
    switch (view.getId()){
        case R.id.button:
            String inputText=editText.getText().toString();
            Toast.makeText(this, inputText, Toast.LENGTH_SHORT).show();
            imageView.setImageResource(R.drawable.image2);
            int progress=progressBar.getProgress();
            progress=progress+10;
            progressBar.setProgress(progress);
            ProgressDialog progressDialog=new ProgressDialog(MainActivity.this);
            progressDialog.setTitle("This is ProgressDialog");
            progressDialog.setMessage("Loading......");
            progressDialog.setCancelable(true);
            progressDialog.show();
            break;
        default:

    }
}

3、布局

一个丰富的界面总是要由很多个控件组成的,那我们如何才能让各个控件都有条不紊地摆放在界面上,而不是乱糟糟的,这就需要借助布局了。布局是一种可以用来放置很多控件的容器,它可以按照一定的规律调整内部控件的位置,从而编写出精美的界面。当然,布局里面还可以放布局,多层嵌套。

3.1、线性布局

android:orientation

LinearLayout称为线性布局,是一种比较常用的布局。这个布局会将它所包含的控件在线性方向上依次排列。线性排列肯定会分为垂直和水平,通过android:orientation控制,如果是垂直就是vertical,如果是水平就是horizontal。

<?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">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button1"
        android:text="Button 1"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button2"
        android:text="Button 2"/>

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

现在我们尝试把布局改成水平的android:orientation=”horizontal”

当然,如果不指定android:orientation,默认的排列方向就是水平的

LinearLayout的排列方式为horizontal时,内部控件就不能将宽度设置为match_parent,这样一个控件就会把整个水平方向占满,其他的控件就没地方放了。同理,如果LinearLayout的排列方式是vertical,那么控件的高度就不能设置为match_parent。

android:layout_gravity
<?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">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button1"
        android:layout_gravity="top"
        android:text="Button 1"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button2"
        android:layout_gravity="center_vertical"
        android:text="Button 2"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button3"
        android:layout_gravity="bottom"
        android:text="Button 3"/>
</LinearLayout>

android:layout_gravity指定控件在布局中的位置,android:gravity是指定文字在控件中的对齐方式,但是他们的可选值差不多。需要注意的是,当android:orientation=”horizontal”,只有垂直方向上的对齐方式才会生效,同理当android:orientation=”vertical”时,只有水平方向的对齐方式才会生效。

android:layout_weight
<?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:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:id="@+id/input_message"
        android:hint="please input something!"
        />
    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:id="@+id/button1"
        android:text="Button 1"/>
</LinearLayout>

这个属性允许我们使用比例的方式指定控件的大小,也就是通过分配权重。这里虽然把layout_width设置为0,但是文本编辑框和按钮都能显示,因为现在的宽度由layout_weight决定,这里写0dp是一种规范的写法。由于EditText和Button的layout_weight都是1,这样就会平分屏幕的宽度,如果想让文本编辑框占3/5,让按钮占2/5,就把对应的layout_weight改成3和2.

3:2

<?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:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:id="@+id/input_message"
        android:hint="please input something!"
        />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button1"
        android:text="Button 1"/>
</LinearLayout>

这里把EditText的android:layout_weight改为1,并且把Button的android:layout_width设置成wrap_content,表示Button还是按wrap_content计算,则EditText会占满屏幕所剩下的空间,对于屏幕的适配比较好。

3.2、相对布局

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">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button1"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:text="button 1"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button2"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:text="button 2"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button3"
        android:layout_centerInParent="true"
        android:text="button 3"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button4"
        android:layout_alignParentLeft="true"
        android:layout_alignParentBottom="true"
        android:text="button 4"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button5"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:text="button 5"/>
</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">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button3"
        android:layout_centerInParent="true"
        android:text="button 3"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button1"
        android:layout_above="@+id/button3"
        android:layout_toLeftOf="@+id/button3"
        android:text="button 1"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button2"
        android:layout_above="@+id/button3"
        android:layout_toRightOf="@+id/button3"
        android:text="button 2"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button4"
        android:layout_below="@+id/button3"
        android:layout_toLeftOf="@+id/button3"
        android:text="button 4"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button5"
        android:layout_below="@+id/button3"
        android:layout_toRightOf="@+id/button3"
        android:text="button 5"/>
</RelativeLayout>

3.3、帧布局

FrameLayout又称帧布局,应用场景比较少,所有的控件都会默认摆放在布局的左上角。

<?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:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/text_view"
        android:text="This is a TextView"/>

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/image_view"
        android:src="@mipmap/ic_launcher"/>
</FrameLayout>

文字和图片都在布局的左上角,由于ImageView是在TextView之后添加的,因此图片压在了文字的上面。

3.4、百分比布局

前面的三种布局从Android1.0就开始支持了,一直沿用到现在,满足了绝大多数的场景界面设计需求。不过只有LinearLayout支持使用layout_weight属性实现按比例指定控件大小的功能,其他的两种布局是不支持的。通过百分比布局可以解决这个问题,我们不再使用wrap_content和match_parent等方式来指定控件的大小,而是允许直接指定控件在布局中所占的百分比,这样的话就可以轻松实现平分布局甚至是任意比例分割布局的效果了。由于LinearLayout本身已经支持按比例指定控件的大小了,因此百分比布局只为FrameLayout和RelativeLayout进行了功能扩展,提供了PercentFrameLayout和PercentRelativeLayout。

百分比布局属于新增布局,为了让新增布局在所有的Android版本上都能使用,为此,Android团队将百分比布局定义在了support库中,我们需要在build.gradle中添加依赖,如图所示。

<?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/button1"
        android:text="Button 1"
        android:layout_gravity="left|top"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"
        android:layout_height="0dp"
        android:layout_width="0dp"/>
    <Button
        android:id="@+id/button2"
        android:text="Button 2"
        android:layout_gravity="right|top"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"
        android:layout_height="0dp"
        android:layout_width="0dp"/>
    <Button
        android:id="@+id/button3"
        android:text="Button 3"
        android:layout_gravity="left|bottom"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"
        android:layout_height="0dp"
        android:layout_width="0dp"/>
    <Button
        android:id="@+id/button4"
        android:text="Button 4"
        android:layout_gravity="right|bottom"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"
        android:layout_height="0dp"
        android:layout_width="0dp"/>
</android.support.percent.PercentFrameLayout>

4、创建自定义控件

![image-20230408131717965](/Users/whitebird/Library/Application Support/typora-user-images/image-20230408131717965.png)

可以看到,我们所用的所有控件都是直接或者间接继承自View的,所用的布局是直接或者间接继承自ViewGroup的。

4.1、引入布局

有时候我们一个布局需要在很多个活动中使用,如果都编写一次同样的代码,会导致代码的大量重复,这时候我们可以通过引入布局来解决这个问题。

首先写一个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">

    <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:text="Back"
        android:textColor="#fff"/>

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:id="@+id/title_text"
        android:layout_gravity="center"
        android:layout_weight="1"
        android:gravity="center"
        android:text="Title Text"
        android:textColor="#f16"
        android:textSize="24sp"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/title_edit"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        android:text="Edit"
        android:textColor="#fff"/>

</LinearLayout>

在activity_main.xml只需要include就行了

<?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"
    >
   <include layout="@layout/title"/>
</LinearLayout>

运行后,布局不在顶部,这是因为系统自带的标题栏,我们可以选择隐藏它

package com.example.myapplication;

import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ActionBar actionBar=getSupportActionBar();
        if (actionBar!=null){
            actionBar.hide();
        }
    }
}

以后的活动想要用这个标题栏,直接include就行了。

4.2、创建自定义控件

public class TitleLayout extends LinearLayout {
    public TitleLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.title, this);
        Button titleBack=(Button) findViewById(R.id.title_back);
        Button 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(context, "You click Edit button", Toast.LENGTH_SHORT).show();

            }
        });
    }
<com.example.myapplication.TitleLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

5、最常用和最难用的控件——ListView

我们几乎所有的应用程序都会用到它,由于手机屏幕有限,能够一次性在屏幕上显示的内容并不多,当我们的程序中有大量的数据需要展示的时候,就可以借助ListView实现,ListView允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时屏幕上的原有的数据则会滚动出屏幕,比如QQ聊天记录、翻阅微博最新消息都是用的这个控件。

5.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">

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

</LinearLayout>

接下来修改 MainActivity.java的代码

package com.example.listview1;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

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);
        ArrayAdapter<String> adapter =new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1,data);
      //android.R.layout.simple_list_item_1 Listview子项布局的id,里面只有一个TextView
        ListView listView=(ListView) findViewById(R.id.list_view);
        listView.setAdapter(adapter);
    }
}

ListView是用于展示大量数据的,那我们就应该先将数据提供好。不过数组中的数据是无法传递给ListView的,我们需要借助适配器来完成——ArrayAdapter,然后将配置好的适配器对象通过setAdapter传递给ListView

5.2、定制ListView的界面

只能显示一段文本的ListView太单调了,我们现在对ListView的界面进行定制,让它可以显示更加丰富的内容。

先定义一个实体类,作为ListView适配器的适配类型。

public class Fruit{
    private String name;
    private int imageId;
    public Fruit(String name,int id){
        this.name=name;
        this.imageId=id;
    }
    public String getName(){
        return name;
    }
    public int getImageId(){
        return imageId;
    }
}

name表示水果的名字,imageId表示水果对应图片的资源id。需要为ListView的子项指定一个我们自定义的布局

<?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">
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id ="@+id/fruit_image"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:id="@+id/fruit_name"
        android:layout_marginLeft="10dp"/>
</LinearLayout>

现在需要重写适配器

package com.example.listview1;

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 org.w3c.dom.Text;

import java.util.List;

public class FruitAdapter extends ArrayAdapter<Fruit> {
    private int resourceId;//用于保存传进来的子项布局id,后面动态加载要用

    public FruitAdapter(Context context, int textviewResourceId, List<Fruit> objects) {
        super(context, textviewResourceId, objects);
        resourceId = textviewResourceId;
    }
	//重写了父类ArrayAdapter中getView
    public View getView(int position, View converView, ViewGroup parent) {//每个子项被滚动到屏幕内会被调用
    Fruit fruit = getItem(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());
        fruitname.setText(fruit.getName());
        return view;
    }
}

主函数

package com.example.listview1;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;


public class MainActivity extends AppCompatActivity {
    private List<Fruit> fruitList =new ArrayList<>();
    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);
        }
    }
    @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);
    }

}

5.3、提升ListView的运行效率

ListView的运行效率很低,因为在FruitAdapter的getView方法中,每次都会重新加载一次布局,当ListView快速滚动时,这就会成为性能瓶颈。getView中有个参数converView,这个参数用于将之前的加载好的布局进行缓存,以便之后可以进行重用。

public View getView(int position, View convertView, ViewGroup parent) {//每个子项被滚动到屏幕内会被调用
        Fruit fruit = getItem(position);//获取当前项的Fruit实例
       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;
   }

对convertView进行判断,如果为空,则使用LayoutInflater加载布局,如果不为空,直接对convertView进行重用。虽然现在已经不会再重复去加载布局,但是每次在getView方法中还是会调用View的findViewById来获取一次控件的实例,我们可以借助一个ViewHolder来对这部分性能进行优化

public View getView(int position, View convertView, ViewGroup parent) {//每个子项被滚动到屏幕内会被调用
      Fruit fruit = getItem(position);//获取当前项的Fruit实例
      View view;
      class ViewHolder {
          ImageView fruitImage;
          TextView fruitName;
      }
      ViewHolder viewHolder;
      if (convertView==null){
          view= LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
          viewHolder =new ViewHolder();
          viewHolder.fruitName=(TextView)view.findViewById(R.id.fruit_name);
          viewHolder.fruitImage=(ImageView) view.findViewById(R.id.fruit_image);
          view.setTag(viewHolder);
      }else {
          view =convertView;
          viewHolder=(ViewHolder)view.getTag();
      }
      viewHolder.fruitImage.setImageResource(fruit.getImageId());
      viewHolder.fruitName.setText(fruit.getName());
      return view;
  }

新增一个内部类ViewHolder,用于对控件的实例进行缓存。当convertView为null,就创建一个ViewHolder对象,并将控件的实例都存放在ViewHolder里,然后调用view的setTag,将ViewHolder存放在view中。当convertView不为空,则调用view的getTag方法,把ViewHolder取出来,里面包含所有控件的实例。

5.4、ListView的点击事件

@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.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();
          }
      });
      listView.setAdapter(adapter);
  }

通过position参数判断用户点击的是哪一个子项,然后获取相应的水果,通过Toast将水果打印出来。

6、滚动控件——RecyclerView

ListView只能实现数据纵向滚动的效果,如果我们想实现横向滚动,就只能依靠一个更强大的控件——RecyclerView,不仅拥有ListView的效果,在优化了ListView中存在的各种不足之处。

6.1、RecyclerView的基本用法

RecyclerView属于新增控件,为了让RecyclerView在所有Android版本上都能使用,使用了添加依赖的方法

implementation 'androidx.recyclerview:recyclerview:1.2.1'

Activity_main编写

<?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">
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

FruitAdapter

package com.example.recyclerviewlist;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
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{
        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 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){
        Fruit fruit=mFruitList.get(position);
        holder.fruitImage.setImageResource(fruit.getImageId());
        holder.fruitName.setText(fruit.getName());
   }
   @Override
   public int getItemCount(){
        return mFruitList.size();
   }
}

MainActivity

package com.example.recyclerviewlist;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList=new ArrayList<>();

        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);
            }
        }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化所有的水果数据
        initFruits();
        //获取recycler_view实例
        RecyclerView recyclerView=(RecyclerView) findViewById(R.id.recycler_view);
        //创建一个LinearLayoutManager 对象
        LinearLayoutManager layoutManager=new LinearLayoutManager(this);
        //LayoutManager用于指定RecyclerView的布局方式 LinearLayoutManager是线性布局
        recyclerView.setLayoutManager(layoutManager);
        //创建FruitAdapter的实例,传入水果数据
        FruitAdapter adapter=new FruitAdapter(fruitList);
        //适配器设置
        recyclerView.setAdapter(adapter);

    }
}

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

由于ListView的扩展性并不好,它只能实现纵向滚动,如果需要横向滚动,就要借助RecyclerView,而且非常简单

首先修改fruit_item里的元素,改为垂直排列,宽度设置为了100dp,指定宽度为固定值,因为每一种水果的文字长度不一致,如果使用wrap_content,子项有长有短就会不美观,如果使用match_parent,导致宽度过长,一个子项占满整个屏幕

<?xml version="1.0" encoding="utf-8"?>
<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>

ImageView和TextView都设置为了在布局中水平居中,使用layout_marginTop属性让文字和图片之间保持一些距离。

接下来修改MainActivity中的代码,只添加了一行代码

//设置布局的排列方向,默认是纵向的,LinearLayoutManager.HORIZONTAL表示让布局横行排列
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);

ListView的布局排列是由自身去管理,而RecyclerView则交给了LayoutManager

瀑布流——GridLayoutManager

首先修改fruit_item.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="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

package com.example.recyclerviewlist;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList=new ArrayList<>();


    private void initFruits() {
        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);
            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();
    }

}
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化所有的水果数据
        initFruits();
        //获取recycler_view实例
        RecyclerView recyclerView=(RecyclerView) findViewById(R.id.recycler_view);
        //创建一个StaggeredGridLayoutManager 实例,第一个参数用于指定布局的列数,传入3表示布局为3列,第二个参数用于指定布局的排列方向
        StaggeredGridLayoutManager layoutManager=new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);
        //创建FruitAdapter的实例,传入水果数据
        FruitAdapter adapter=new FruitAdapter(fruitList);
        //适配器设置
        recyclerView.setAdapter(adapter);
    }
}

瀑布流效果只有各个子项高度不一样时才能看出明显的效果,所以我们通过getRandomLengthName方法,将传入的字符串重复随机遍

6.3、RecyclerView的点击事件

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

package com.example.recyclerviewlist;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
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{
        ImageView fruitImage;
        TextView fruitName;
        View fruitView;
        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 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();
   }
}

7、实践——聊天界面

7.1、制作Nine-Patch图片

是一种被特殊处理的png图片,能够指定哪些区域可以被拉伸,哪些区域不可以。

左边和上面的黑线控制拉伸,右边和下面的黑线控制内容显示区域

7.2、编写聊天界面

<?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="#d8e0e8"
    >
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/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"
        android:orientation="horizontal">
        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:id="@+id/input_text"
            android:layout_weight="1"
            android:hint="Type something here"
            android:maxLines="2"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/send"
            android:text="Send"/>
    </LinearLayout>
</LinearLayout>

我们在主界面中放置了一个RecyclerView用于显示聊天的消息内容,又放置了一个EditText用于输入消息,还放置了一个Button用于发送消息。

定义一个消息类

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

编写子项布局

<?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="#d8e0e8"
    >
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/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"
        android:orientation="horizontal">
        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:id="@+id/input_text"
            android:layout_weight="1"
            android:hint="Type something here"
            android:maxLines="2"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/send"
            android:text="Send"/>
    </LinearLayout>
</LinearLayout>

创建适配器

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

这个和之前的代码基本一样,只不过在onBindViewHolder中加了对消息类型的判断,如果这条消息是收到的,则显示左边的消息布局,如果这条消息是发出的,则显示右边的消息布局。

MainActivity

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(); // 初始化消息数据
        inputText = (EditText) findViewById(R.id.input_text);
        send = (Button) findViewById(R.id.send);
        msgRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        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 v) {
                String content = inputText.getText().toString();
                if (!"".equals(content)) {
                    Msg msg = new Msg(content, Msg.TYPE_SENT);
                    msgList.add(msg);
                    //当有新消息时,刷新RecyclerView中的显示
                    adapter.notifyItemInserted(msgList.size() - 1);
                    msgRecyclerView.scrollToPosition(msgList.size() - 1); // 将ListView定位到最后一行
                    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 Tom. Nice talking to you. ", Msg.TYPE_RECEIVED);
        msgList.add(msg3);
    }

}

![image-20230412155620355](/Users/whitebird/Library/Application Support/typora-user-images/image-20230412155620355.png)


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 767778848@qq.com