zoukankan      html  css  js  c++  java
  • Android第一行代码(第二版)读书笔记2

    第三章 UI开发

    3.2 常用的控件

    3.2.1 TextView

    • android:gravity指文字的对齐方式:top/bottom/left/right/center
    • android:textSize="24sp" android中字体大小以sp为单位

    3.2.2 Button

    Button点击事件的注册模式有两种,一是以匿名类方式,二是以继承接口的方式.
    匿名类方式示例如下:

    public class FirstActivity extends BaseActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.first_layout);
            Button button1 = findViewById(R.id.button1);
            button1.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                   // 业务逻辑
                }
            });
        }
    }
    

    继承接口方式示例:

    public class ThirdActivity extends BaseActivity implements View.OnClickListener {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.third_layout);
            Button button = findViewById(R.id.button3);
            button.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View view) {
            switch (view.getId()){
                case R.id.button3:
                    ActivityCollector.finishAll();
                    Log.d("ThirdActivity", String.format("Process exit %s", android.os.Process.myPid()));
                    android.os.Process.killProcess(android.os.Process.myPid());
                    break;
                default:
                    break;
            }
        }
    }
    

    3.2.3 EditText

    • android:hint 可以给EditText一个提示性的文本
    • android:maxLines 指定了EditText的最大行数为两行,超过两行后文本自动向上滚动而不会在继续拉伸
      增加一个button来获取EditText输入的内容,button使用继承的方式来实现监听.新建项目UIWidgetTest,MainActivity代码如下:
    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 button = findViewById(R.id.button);
            button.setOnClickListener(this);
            editText = findViewById(R.id.edit_text);
        }
    
        @Override
        public void onClick(View view) {
            switch (view.getId()){
                case R.id.button:
                    String inputText = editText.getText().toString();
                    Toast.makeText(MainActivity.this,inputText,Toast.LENGTH_SHORT).show();
                    break;
                default:
                    break;
            }
    
        }
    }
    

    3.2.4 ImageView

    ImageView是一个展示图片的控件,图片一般是放在drawable开头的目录下.默认项目会创建一个空的drawable目录,不过由于这个目录没有指定具体的分辨率,所以一般不使用此目录来放置图片.在res下新建drawable-xhdpi目录,并将准备好的图片复制到目录中.在android视图下不显示新建的目录,可以切换到project视图下.在新建drawable-xhdpi目录时,需要将Available qualifiers属性选择为Density并选择对应的分辨率.
    修改activity_main.xml来显示图片,因为未知图片的宽和高,所以设置为wrap_content,保证图片完全显示.代码如下:

    <?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">
        <EditText
            android:id="@+id/edit_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Type something"
            android:maxLines="2" />
        <Button
            android:id="@+id/button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="button"></Button>
        <ImageView
            android:id="@+id/image_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/img7"></ImageView>
    </LinearLayout>
    

    通过程序可以动态的修改ImageView中显示的图片,修改MainActivity代码后如下:

    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 button = findViewById(R.id.button);
            editText = findViewById(R.id.edit_text);
            imageView = findViewById(R.id.image_view);
            button.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View view) {
            switch (view.getId()) {
                case R.id.button:
                    String inputText = editText.getText().toString();
                    Toast.makeText(MainActivity.this, inputText, Toast.LENGTH_SHORT).show();
                    imageView.setImageResource(R.drawable.img8);
                    break;
                default:
                    break;
            }
    
        }
    }
    
    

    3.2.5 ProgressBar

    progressBar用于显示进度条,基本用法如下:

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

    默认情况下.进度是可见的.所有的Android控件的都有可见属性,通过android:visibility来指定,可选参数有:visible,invisible,gone.

    • visible :可见
    • invisable:不可见,但占位置
    • gnoe: 不可见,不占位置
      在MainActivity中增加一个Button,用来测试修改progressBar的状态.修改后的avtivity_main.xml如下:
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <ProgressBar
            android:id="@+id/progress_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
        <Button
            android:id="@+id/progress_button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="change ProgressBar"/>
    </LinearLayout>
    
    

    修改后MainActivity.java如下

    
    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 button = findViewById(R.id.button);
            editText = findViewById(R.id.edit_text);
            imageView = findViewById(R.id.image_view);
            button.setOnClickListener(this);
    
            progressBar = findViewById(R.id.progress_bar);
            Button processButton = findViewById(R.id.progress_button);
            processButton.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View view) {
            switch (view.getId()) {
                case R.id.button:
                    String inputText = editText.getText().toString();
                    Toast.makeText(MainActivity.this, inputText, Toast.LENGTH_SHORT).show();
                    imageView.setImageResource(R.drawable.img8);
                    break;
                case R.id.progress_button:
                   if (progressBar.getVisibility() == View.GONE) {
                       progressBar.setVisibility(View.VISIBLE);
                   } else {
                       progressBar.setVisibility(View.GONE);
                   }
                   break;
                default:
                    break;
            }
    
        }
    }
    

    默认情况下ProgressBar是圆形的,可以修改其样式,比如改成水平进度条

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

    style定义了样式,max属性给进度条设置了一个最大值.修改MainActivity.java,使每点击一次按钮,进度条进度前进一下:

    
    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 button = findViewById(R.id.button);
            editText = findViewById(R.id.edit_text);
            imageView = findViewById(R.id.image_view);
            button.setOnClickListener(this);
    
            progressBar = findViewById(R.id.progress_bar);
            Button processButton = findViewById(R.id.progress_button);
            processButton.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View view) {
            switch (view.getId()) {
                case R.id.button:
                    String inputText = editText.getText().toString();
                    Toast.makeText(MainActivity.this, inputText, Toast.LENGTH_SHORT).show();
                    imageView.setImageResource(R.drawable.img8);
                    break;
                case R.id.progress_button:
    //                修改可见性
    //                if (progressBar.getVisibility() == View.GONE) {
    //                    progressBar.setVisibility(View.VISIBLE);
    //                } else {
    //                    progressBar.setVisibility(View.GONE);
    //                }
                    int progress = progressBar.getProgress();
                    progress = progress+10;
                    progressBar.setProgress(progress);
                    break;
                default:
                    break;
            }
    
        }
    }
    

    3.2.6 AlertDialog

    AlertDialog可以在当前的界面弹出一个对话框,这个对话框位置置于所有元素之上,能够屏蔽掉其他控件的交互能力.AlertDialog通过AlertDialog.Builder来实现.setPositiveButton()方法设置对话框确定按钮的事件,setNegativeButton()方法设置取消按钮的事件.修改MainActivity.java如下,点击更新进度的按钮后,弹出提示.dialog.setCancelable(false)表示不能通过Back键取消掉.

    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 button = findViewById(R.id.button);
            editText = findViewById(R.id.edit_text);
            imageView = findViewById(R.id.image_view);
            button.setOnClickListener(this);
            progressBar = findViewById(R.id.progress_bar);
            Button processButton = findViewById(R.id.progress_button);
            processButton.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View view) {
            switch (view.getId()) {
                case R.id.button:
                    String inputText = editText.getText().toString();
                    Toast.makeText(MainActivity.this, inputText, Toast.LENGTH_SHORT).show();
                    imageView.setImageResource(R.drawable.img8);
                    break;
                case R.id.progress_button:
    //                修改可见性
    //                if (progressBar.getVisibility() == View.GONE) {
    //                    progressBar.setVisibility(View.VISIBLE);
    //                } else {
    //                    progressBar.setVisibility(View.GONE);
    //                }
    //                测试进度
                    int progress = progressBar.getProgress();
                    progress = progress+10;
                    progressBar.setProgress(progress);
    //                AlertDialog Demo
                    AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
                    dialog.setTitle(String.format("Title 进度---%s", progress));
                    dialog.setMessage(String.format("Message 进度---%s", progress));
                    dialog.setCancelable(false);
                    dialog.setPositiveButton("ok", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {
                            Log.d("MainActivity","It's ok");
                        }
                    });
                    dialog.setNegativeButton("取消", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {
                            Log.d("MainActivity", "取消");
                        }
                    });
                    dialog.show();
                    break;
                default:
                    break;
            }
    
        }
    }
    

    3.2.7 ProgressDialog

    progressDialog和AlertDialog有点类似,都是弹出一个对话框,不同的是,ProgressDialog会在对话框中先是一个进度条,一般用于表示当前操作耗时.另外当数据加载后,必须带调用ProgressDialog的dismiss()方法来关闭对话框.
    现在模拟一个ProgressDialog,在打开对话框后向另一个Activity发起一个Intent,并获取其返回的数据,数据返回后,关闭此progressDialog.
    新建一个activity起名ProgressDialogBack,用来返回数据给MainAcitivity.代码如下:

    public class ProgressDialogBack extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_progress_dialog_back);
            Intent intent1  = new Intent();
            intent1.putExtra("data_return","data is ok");
            setResult(RESULT_OK,intent1);
            finish();
        }
    }
    

    修改activity_main.xml及MainActivity.java,增加一个button并绑定事件,生成一个progressDialog,同时获取返回值并关闭progressDialog.MainActivity.java代码如下activity_main.xml代码略.

    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        private EditText editText;
        private ImageView imageView;
        private ProgressBar progressBar;
        private ProgressDialog progressDialog;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Button button = findViewById(R.id.button);
            editText = findViewById(R.id.edit_text);
            imageView = findViewById(R.id.image_view);
            button.setOnClickListener(this);
    
            progressBar = findViewById(R.id.progress_bar);
            Button processButton = findViewById(R.id.progress_button);
            processButton.setOnClickListener(this);
    
            progressDialog = new ProgressDialog(MainActivity.this);
            Button progress_dialog_button = findViewById(R.id.progress_dialog_button);
            progress_dialog_button.setOnClickListener(this);
    
    
        }
    
    
        @Override
        public void onClick(View view) {
            switch (view.getId()) {
                case R.id.button:
                    String inputText = editText.getText().toString();
                    Toast.makeText(MainActivity.this, inputText, Toast.LENGTH_SHORT).show();
                    imageView.setImageResource(R.drawable.img8);
                    break;
                case R.id.progress_button:
    //                修改可见性
    //                if (progressBar.getVisibility() == View.GONE) {
    //                    progressBar.setVisibility(View.VISIBLE);
    //                } else {
    //                    progressBar.setVisibility(View.GONE);
    //                }
    //                测试进度
                    int progress = progressBar.getProgress();
                    progress = progress + 10;
                    progressBar.setProgress(progress);
    //                AlertDialog Demo
                    AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
                    dialog.setTitle(String.format("Title 进度---%s", progress));
                    dialog.setMessage(String.format("Message 进度---%s", progress));
                    dialog.setCancelable(false);
                    dialog.setPositiveButton("ok", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {
                            Log.d("MainActivity", "It's ok");
                        }
                    });
                    dialog.setNegativeButton("取消", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {
                            Log.d("MainActivity", "取消");
                        }
                    });
                    dialog.show();
                    break;
                case R.id.progress_dialog_button:
                    progressDialog.setTitle("This is Progress Dialog");
                    progressDialog.setMessage("Loading...");
                    progressDialog.setCancelable(true);
                    progressDialog.show();
                    Intent intent = new Intent(MainActivity.this, ProgressDialogBack.class);
                    startActivityForResult(intent, 1);
                    break;
                default:
                    break;
            }
        }
    
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            switch (requestCode) {
                case 1:
                    if (resultCode == RESULT_OK) {
                        String returnData = data.getStringExtra("data_return");
                        Log.d("MainActivity", returnData);
                        try {
                            Thread.sleep(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    progressDialog.dismiss();
                    break;
                default:
            }
        }
    }
    

    需要注意的是,progressDialog已经被弃用,官方指南中有说明如下:

    注意:Android 包含另一种名为 ProgressDialog 的对话框类,该类可显示带有进度条的对话框.不推荐使用此微件,因为它会在显示进度的情况下阻止用户与应用交互.如果需要指示加载进度或不确定的进度,您应遵循进度和 Activity 的设计指南,并在布局中使用 ProgressBar,而非 ProgressDialog.

    该类已在API级别26中弃用.
    ProgressDialog是模式对话框,可防止用户与应用进行交互.而不是使用此类,您应该使用进度指示器(例如)android.widget.ProgressBar,可以将其嵌入到应用的用户界面中.或者,您可以使用通知来通知用户任务的进度.

    3.3 4种基本布局

    3.3.1 线性布局

    LinearLayout 线性布局

    • android:orientation="vertical" 垂直排列
    • android:orientation="horizontal" 水平排列
    • android:layout_height="wrap_content" 强制性地使视图扩展以显示全部内容
    • android:layout_width="match_parent" 布满整个屏幕

    使用horizontal水平排列时,不能将控件的宽度指为match_parent,否则一个控件将在水平方向占满

    • android:layout_gravity="center" 布局控件的对齐方式

    修改activity_progress_dialog_back.xml来测试线性布局
    在有属性android:layout_weight时,android:layout_width属性就不会生效,指定成0dp时规范的写法.dp是Android中用于指定控件大小间距等单位.
    android:layout_weight="1" 表示EditText和Button在水平方向平分,系统会把LinearLayout下所有的控件的layout_weight值相加,并计算每个控件占据屏幕宽度的比例.

    <?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">
        <EditText
            android:id="@+id/input_message"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="Typing"
            />
        <Button
            android:id="@+id/send"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="send"/>
    </LinearLayout>
    

    为了获取更好的展示效果,修改布局文件如下:

    <?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">
        <EditText
            android:id="@+id/input_message"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="Typing"
            />
        <Button
            android:id="@+id/send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="send"/>
    </LinearLayout>
    

    3.3.2 相对布局

    3.3.3 帧布局

    3.3.4 百分比布局

    3.4 自定义控件

    View是Android中最基本的UI组件,ViewGroup是一种特殊的View,用于防止控件和布局容器.新建一个UICustomViews项目进行测试自定义组件.

    3.4.1 引入布局

    在layout下新建一个布局文件,起名为title.xml,代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/title_bg">
    
        <Button
            android:id="@+id/title_back"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="5dp"
            android:background="@drawable/back_bg"
            android:text="BACK"
            android:textColor="#fff"/>
    
        <TextView
            android:id="@+id/title_text"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_weight="1"
            android:textColor="#fff"
            android:text="Title Text"
            android:textSize="24sp"/>
    
        <Button
            android:id="@+id/title_edit"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:background="@drawable/edit_bg"
            android:layout_margin="5dp"
            android:text="Edit"
            android:textColor="#fff"/>
    </LinearLayout>
    

    在此布局中有两个按钮,一个TextView.左边的Button为返回键,右侧为编辑,中间显示标题.背景使用颜色或图片.代码中android:layout_margin="5dp"属性表示空间在上下左右方向的便宜距离.修改activity_main.xml中代码,将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="match_parent">
        <include layout="@layout/title"/>
    </LinearLayout>
    

    最后修改MainActivity.java将系统中自带的标题栏隐掉,代码如下:

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

    3.4.2 自定义控件

    引入布局解决了重复编写布局代码问题,但是如果布局中的一些控件需要进行响应事件,则需要使用自定义控件方式解决.新建TitleLayout类并继承LinearLayout,作为自定义的标题控件.代码如下:

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

    重写LinearLayout中的带有两个参数的构造函数,from()方法可以构建出一个LayoutInflater对象,inflate()方法用来动态加载布局文件,inflate方法接收两个参数,一个是布局文件,一个是加载布局文件的父布局,这里为当前对象.

    自定义控件创建好后,修改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">
    <!--    <include layout="@layout/title"/>-->
        <com.example.uicustomviews.TitleLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>
    

    添加自定义控件的时候,需要指明控件的完整类名,修改TitleLayout中的代码,给按钮注册事件,代码如下:

    public class TitleLayout extends LinearLayout {
        public TitleLayout(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            LayoutInflater.from(context).inflate(R.layout.title, this);
            Button titleBackButton = findViewById(R.id.title_back);
            Button titleEditButton = findViewById(R.id.title_edit);
            titleBackButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    ((Activity) getContext()).finish();
                }
            });
            titleEditButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(getContext(),"You click edit button",Toast.LENGTH_SHORT).show();
                }
            });
        }
    }
    

    此时自定义控件封装完毕.

    3.5 ListView

    3.5.1 ListView的简单用法

    新建一个ListViewTest项目,修改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中代码,初始化一些测试数据,

    public class MainActivity extends AppCompatActivity {
    
        private String[] data = new String[20];
    
        private void initData() {
            StringBuilder stringBuilder = new StringBuilder("Cherry");
            for (int i = 0; i < 20; i++) {
                data[i] = stringBuilder.append(i).toString();
                stringBuilder.setLength(6);
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initData();
    
            ArrayAdapter<String> adapter = new ArrayAdapter<String>(
                    MainActivity.this, android.R.layout.simple_list_item_1, data);
            ListView listView = findViewById(R.id.list_view);
            listView.setAdapter(adapter);
        }
    }
    

    在这里数组是无法单独传递给ListView的,需要使用Android提供的适配器实现类,这里使用的是ArrayAdapter,此外还有CursorAdapter、SimpleAdapter等.ArrayAdapter有多种构造函数,次数使用的是传入构造函数的上下文,子布局id及对应的数据.其中上下文这里为当前类,子布局id为android.R.layout.simple_list_item_1,这是Android内置的布局文件,内容只有一个TextView.最后调用setAdapter完成关联.

    3.5.2 定制ListView的界面

    上面的例子中,内置的子布局只有一个TextView,现在做一个自定义的ListView,为数据显示一组图片(图片文件提前复制到drawable内).首先建立一个Fruit类,用来表示数据的名称和id(此id为Android组件生成的R.id),代码如下:

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

    添加一个ListView的子项的自定义布局文件,起名为fruit_item.xml.再上一个例子中使用的是Android内置的布局文件android.R.layout.simple_list_item_1.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="wrap_content">
        <ImageView
            android:id="@+id/fruit_image"
            android:layout_width="40dp"
            android:layout_height="40dp"/>
    
        <TextView
            android:id="@+id/fruit_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="10dp" />
    </LinearLayout>
    

    现在创建一个自定义的适配器类,并继承ArrayAdapter,通过泛型指定为Fruit类.新建的类名为FruitAdapter,代码如下:

    public class FruitAdapter extends ArrayAdapter {
    
        private int resourceId;
    
        public FruitAdapter(@NonNull Context context, int resource, List<Fruit> objects) {
            super(context, resource,objects);
            resourceId = resource;
        }
    
        @Override
        public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
    //        return super.getView(position, convertView, parent);
            Fruit fruit = (Fruit) getItem(position);
            View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
            ImageView fruitImage = view.findViewById(R.id.fruit_image);
            TextView fruitName = view.findViewById(R.id.fruit_name);
            fruitImage.setImageResource(fruit.getImageId());
            fruitName.setText(fruit.getName());
            return view;
        }
    }
    

    FruitAdapter重写了父类的构造函数,用于将上下文、ListView的子项布局的id和数据都传递进来.另外重写了getView()方法,此方法会在每个子项目滚动到屏幕内的时候调用,在此方法中,想通过getItem()方法获取当前位置的Fruit实例,然后使用LayoutInflater来加载子布局.LayoutInflater的使用在上一个例子(3.4.1)中已经使用过,而这里的第三个参数false表示只让我们在父布局中声明的layout属性生效,但不为这个View添加父布局(这块暂时没太理解,mark).后面代码比较简单,即设置当前位置的对象的值,将传入的值和自定义 子布局中的控件关联上.

    修改MainActivity中的代码如下:

    public class MainActivity extends AppCompatActivity {
    
        private String[] data = new String[20];
        private List<Fruit> fruitList = new ArrayList<Fruit>(20);
    
        private void initData() {
            StringBuilder stringBuilder = new StringBuilder("Cherry");
            for (int i = 0; i < 20; i++) {
                data[i] = stringBuilder.append(i).toString();
                stringBuilder.setLength(6);
            }
        }
    
        /**
         * 自定义子布局初始化数据
         */
        private void initFruit(){
            Fruit fruit =null;
            StringBuilder stringBuilder = new StringBuilder("Cherry");
            StringBuilder drawableName = new StringBuilder("shuzi");
            for (int i = 1; i < 21; i++) {
                int resID = getResources().getIdentifier(drawableName.append(i).toString() , "drawable", getPackageName());
                fruit = new Fruit(stringBuilder.append(i).toString(),resID);
                fruitList.add(fruit);
                stringBuilder.setLength(6);
                drawableName.setLength(5);
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    //        initData();
    //
    //        ArrayAdapter<String> adapter = new ArrayAdapter<String>(
    //                MainActivity.this, android.R.layout.simple_list_item_1, data);
    //        ListView listView = findViewById(R.id.list_view);
    //        listView.setAdapter(adapter);
    
            initFruit();
            FruitAdapter fruitAdapter = new FruitAdapter(MainActivity.this,R.layout.fruit_item,fruitList);
            ListView listview = findViewById(R.id.list_view);
            listview.setAdapter(fruitAdapter);
        }
    }
    

    这里为了方便动态的加载图片文件名称,使用了以下方式来获取图片的id

     int resID = getResources().getIdentifier(drawableName.append(i).toString() , "drawable", getPackageName());
    

    如果单独初始化一个对象,应该按以下方式使用:

    Fruit fruit = new Fruit("水果",R.drawable.xxx);
    

    3.5.3 提升ListView的运行效率

    在3.5.2的例子中,FruitAdapter的getView()方法每次都将布局重新加载了一遍,当ListView快速滚动式,可能造成系统瓶颈.在getView方法中有一个convertView的参数,这个参数可以将布局进行缓存,修改FruitAdapter

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

    此时,可以提高ListView的运行效率.但在获取fruitImage和fruitName时仍然回去访问控件实例,此时可以借助新建一个ViewHolder的类进行性能优化,在FruitAdapter中新建内部类ViewHolder,完整代码如下:

    public class FruitAdapter extends ArrayAdapter {
    
        private int resourceId;
    
        public FruitAdapter(@NonNull Context context, int resource, List<Fruit> objects) {
            super(context, resource, objects);
            resourceId = resource;
        }
    
        @Override
        public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
            Fruit fruit = (Fruit) getItem(position);
            View view;
            ViewHolder viewHolder;
    //        View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
            if (convertView == null) {
                view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
                viewHolder = new ViewHolder();
                viewHolder.imageView = view.findViewById(R.id.fruit_image);
                viewHolder.textView = view.findViewById(R.id.fruit_name);
                view.setTag(viewHolder);//将ViewHolder存储到View中
            }else {
                view = convertView;
                viewHolder = (ViewHolder) view.getTag();
            }
            viewHolder.imageView.setImageResource(fruit.getImageId());
            viewHolder.textView.setText(fruit.getName());
    //        ImageView fruitImage = view.findViewById(R.id.fruit_image);
    //        TextView fruitName = view.findViewById(R.id.fruit_name);
    //        fruitImage.setImageResource(fruit.getImageId());
    //        fruitName.setText(fruit.getName());
            return view;
        }
        class ViewHolder{
            ImageView imageView;
            TextView textView;
        }
    }
    

    setTag()方法的介绍如下,按照个人理解,使用View的Tag可以存储一些数据对象而不需要自定义其他的数据结构,并在需要时通过getTag取出来,这样就完成了数据的缓存

    /**
      * Sets the tag associated with this view. A tag can be used to mark
      * a view in its hierarchy and does not have to be unique within the
      * hierarchy. Tags can also be used to store data within a view without
      * resorting to another data structure.
      *
      * @param tag an Object to tag the view with
      *
      * @see #getTag()
      * @see #setTag(int, Object)
      */
     public void setTag(final Object tag) {
         mTag = tag;
     }
    

    3.5.4 ListView的点击事件

    如果ListView中的子项不能点击的话就没什么意义了,修改MainActivity中代码响应点击事件

    public class MainActivity extends AppCompatActivity {
       ...
        private List<Fruit> fruitList = new ArrayList<Fruit>(20);
       ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    //        initData();
    //
    //        ArrayAdapter<String> adapter = new ArrayAdapter<String>(
    //                MainActivity.this, android.R.layout.simple_list_item_1, data);
    //        ListView listView = findViewById(R.id.list_view);
    //        listView.setAdapter(adapter);
    
            initFruit();
            FruitAdapter fruitAdapter = new FruitAdapter(MainActivity.this,R.layout.fruit_item,fruitList);
            ListView listview = findViewById(R.id.list_view);
            listview.setAdapter(fruitAdapter);
    //        子布局的点击事件
            listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    String imgMessage = String.format("Click img %s", position+1);
                    Fruit fruit = fruitList.get(position);
                    Toast.makeText(MainActivity.this,fruit.getName(),Toast.LENGTH_SHORT).show();
                    Log.d("MainActivity",fruit.getName());
                }
            });
        }
    }
    
    

    此时当点击ListView中的任何一个子项目时,就会调用onItemClick方法,根据position来判断用户点击的是哪个子项目.这里的position和id分别表示view的位置和row的id,如下介绍

     /**
      * Callback method to be invoked when an item in this AdapterView has
      * been clicked.
      * <p>
      * Implementers can call getItemAtPosition(position) if they need
      * to access the data associated with the selected item.
      *
      * @param parent The AdapterView where the click happened.
      * @param view The view within the AdapterView that was clicked (this
      *            will be a view provided by the adapter)
      * @param position The position of the view in the adapter.
      * @param id The row id of the item that was clicked.
      */
     void onItemClick(AdapterView<?> parent, View view, int position, long id);
    

    根据以上例子,考虑如果想在子布局的每一项单独在绑定一个事件,那么在FruitAdapter中实现、在MainActivity中实现、两个类中都实现,那么响应用户点击的顺序是什么样呢?于是修改代码FruitAdapter及MainActivity如下所示:
    FruitAdapter.java

    public class FruitAdapter extends ArrayAdapter {
    
        private int resourceId;
    
        public FruitAdapter(@NonNull Context context, int resource, List<Fruit> objects) {
            super(context, resource, objects);
            resourceId = resource;
        }
    
        @Override
        public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
            Fruit fruit = (Fruit) getItem(position);
            View view;
            ViewHolder viewHolder;
    //        View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
            if (convertView == null) {
                view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
                viewHolder = new ViewHolder();
                viewHolder.imageView = view.findViewById(R.id.fruit_image);
                viewHolder.textView = view.findViewById(R.id.fruit_name);
                view.setTag(viewHolder);//将ViewHolder存储到View中
            }else {
                view = convertView;
                viewHolder = (ViewHolder) view.getTag();
            }
            viewHolder.imageView.setImageResource(fruit.getImageId());
            viewHolder.textView.setText(fruit.getName());
            //----
            //测试绑定点击响应
            viewHolder.imageView.setOnClickListener(v -> Log.d("FruitAdapter","FruitAdapter click event"));
            //-----
    //        ImageView fruitImage = view.findViewById(R.id.fruit_image);
    //        TextView fruitName = view.findViewById(R.id.fruit_name);
    //        fruitImage.setImageResource(fruit.getImageId());
    //        fruitName.setText(fruit.getName());
            return view;
        }
        class ViewHolder{
            ImageView imageView;
            TextView textView;
        }
    }
    
    

    MainActivity.java

    public class MainActivity extends AppCompatActivity {
    
        private String[] data = new String[20];
        private List<Fruit> fruitList = new ArrayList<Fruit>(20);
    
        private void initData() {
            StringBuilder stringBuilder = new StringBuilder("Cherry");
            for (int i = 0; i < 20; i++) {
                data[i] = stringBuilder.append(i).toString();
                stringBuilder.setLength(6);
            }
        }
    
        /**
         * 自定义子布局初始化数据
         */
        private void initFruit(){
            Fruit fruit =null;
            StringBuilder stringBuilder = new StringBuilder("Cherry");
            StringBuilder drawableName = new StringBuilder("shuzi");
            for (int i = 1; i < 21; i++) {
                int resID = getResources().getIdentifier(drawableName.append(i).toString() , "drawable", getPackageName());
                fruit = new Fruit(stringBuilder.append(i).toString(),resID);
                fruitList.add(fruit);
                stringBuilder.setLength(6);
                drawableName.setLength(5);
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    //        initData();
    //
    //        ArrayAdapter<String> adapter = new ArrayAdapter<String>(
    //                MainActivity.this, android.R.layout.simple_list_item_1, data);
    //        ListView listView = findViewById(R.id.list_view);
    //        listView.setAdapter(adapter);
    
            initFruit();
            FruitAdapter fruitAdapter = new FruitAdapter(MainActivity.this,R.layout.fruit_item,fruitList);
            ListView listview = findViewById(R.id.list_view);
            listview.setAdapter(fruitAdapter);
    //        子布局的点击事件
            listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    String imgMessage = String.format("Click img %s", position+1);
                    Fruit fruit = fruitList.get(position);
    
                    Toast.makeText(MainActivity.this,fruit.getName(),Toast.LENGTH_SHORT).show();
                    Log.d("MainActivity",fruit.getName());
                    //---
                    //测试获取子组件绑定点击事件
    //              ImageView imageView =  view.findViewById(R.id.fruit_image);
                    ImageView imageView = (ImageView) ((LinearLayout)view).getChildAt(0);
                    imageView.setOnClickListener(v -> {
                        Log.d("MainActivity",imgMessage);
                        Toast.makeText(MainActivity.this,imgMessage,Toast.LENGTH_SHORT).show();
                    });
                    //-----
                }
            });
        }
    }
    

    测试发现如果在FruitAdapter中和MainActivity中同时加入imageView的点击,并不会多次执行,但是每次点击时,响应的事件却不同.此处作为遗留问题,后续继续研究下.

    3.6 RecyclerView

    ListView需要优化运行效率,同时只能实现纵向滚动,无法横向滚动.而RecyclerView可以说是一个增强的ListView.新建一个RecycleViewTest项目来测试.

    3.6.1 RecyclerView的基本用法

    原书中描述RecyclerView定义在了support库中,但是本次测试时基于android9(API-28),所以略有差异.根据官方指南,在gradle中增加依赖,如下.增加后需要同步一下.

    dependencies {
        implementation "androidx.recyclerview:recyclerview:1.1.0"
        // For control over item selection of both touch and mouse driven selection
        implementation "androidx.recyclerview:recyclerview-selection:1.1.0-rc01"
       }
    

    修改activity_main.xml中代码,修改为LinearLayout布局并增加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="match_parent">
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </LinearLayout>
    

    在上面代码中,因为RecyclerView不是SDK内置组件,所以需要指定完整的包路径.

    之后将3.5中的Fruit与fruit_item.xml的布局文件及图片文件复制进来即可.新建一个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 fruitText;
    
            public ViewHolder(@NonNull View itemView) {
                super(itemView);
                fruitImage = itemView.findViewById(R.id.fruit_image);
                fruitText = itemView.findViewById(R.id.fruit_name);
            }
        }
    
        public FruitAdapter(List<Fruit> fruitList) {
            this.mFruitList = fruitList;
        }
    
        @NonNull
        @Override
        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
            ViewHolder viewHolder = new ViewHolder(view);
            return viewHolder;
        }
    
        @Override
        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
            Fruit fruit = mFruitList.get(position);
            holder.fruitImage.setImageResource(fruit.getImageId());
            holder.fruitText.setText(fruit.getName());
        }
    
        @Override
        public int getItemCount() {
            return mFruitList.size();
        }
    
    
    }
    

    首先,内部类ViewHolder,这个ViewHolder需要继承RecyclerView.ViewHolder.同时构造方法需要传入一个View参数,这个参数对应的通常是RecyclerView子项的最外层布局,这样就可以通过findViewById()方法来获取布局中的ImageView和TextView组件实例.

    FruitAdapter也有一个构造函数,用来传入数据源,这里是mFruitList.

    FruitAdapter继承了RecyclerView.Adapter,那么就必须重写onCreateViewHolder()、onBindViewHolder()、getItemCount()这三个方法.

    • onCreateViewHolder() 用来创建ViewHolder实例,并把加载的布局传入到构造函数中,最后返回对应的泛型实例,这里是ViewHolder
    • onBindViewHolder() 用来对子项数据进行赋值,会在每个子项数据滚动到屏幕内时执行,通过position参数获取当前的实例对象
    • getItemCount() 用来告诉RecyclerView数据一共有多少

    修改MainActivity中代码,并测试结果

    public class MainActivity extends AppCompatActivity {
        private List<Fruit> fruitList = new ArrayList<>(20);
    
        /**
         * 自定义子布局初始化数据
         */
        private void initFruits(){
            Fruit fruit =null;
            String nameStr = "Cherry%s";
            StringBuilder drawableName = new StringBuilder("shuzi");
            for (int i = 1; i < 21; i++) {
                int resID = getResources().getIdentifier(drawableName.append(i).toString() , "drawable", getPackageName());
                fruit = new Fruit(String.format(nameStr,i),resID);
                fruitList.add(fruit);
                drawableName.setLength(5);
            }
        }
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            initFruits();
            RecyclerView recyclerView = findViewById(R.id.recycler_view);
            LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
            recyclerView.setLayoutManager(linearLayoutManager);
            FruitAdapter fruitAdapter = new FruitAdapter(fruitList);
            recyclerView.setAdapter(fruitAdapter);
        }
    }
    
    

    LayoutManager用于指定RecyclerView的布局方式,这里使用的是LIneraLayoutManager.S

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

    首先对fruit_item.xml布局进行修改,因为目前的这个里的布局是水平排列,适用于纵向滚动的场景.要实现横向滚动,则需要把fruit_item中的布局改为垂直排列比较合理,修改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="wrap_content">-->
    <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="40dp"
            android:layout_height="40dp"
            android:background="#85C1E9"
            android:layout_gravity="center"/>
    
        <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,之后将ImageView与TextView的布局改为水平居中.修改MainActivity中代码,将布局管理器中的布局改为Horizon.这里是将LinearLayoutManager中设置为Horizonal.代码如下:

    
    public class MainActivity extends AppCompatActivity {
       ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initFruits();
            RecyclerView recyclerView = findViewById(R.id.recycler_view);
            LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
            linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
            recyclerView.setLayoutManager(linearLayoutManager);
            FruitAdapter fruitAdapter = new FruitAdapter(fruitList);
            recyclerView.setAdapter(fruitAdapter);
        }
      ...
    }
    
    

    在RecyclerView中,将布局的排列交给了LayoutManager管理,子类只需要按照LayoutManager的规范了来定义就可以实现不通的排列布局.
    除了LinearLayout外,RecyclerView还提供了GridLayoutManager和StaggeredGridManager这两种内置的布局排列.GirdLayoutManager用于实现网格的布局,StaggeredGridLayoutManager用来实现瀑布流式布局.下面来实现一个瀑布流布局.
    复制一个fruit_item.xml起名为fruit_item_staggered.xml,修改其中代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:orientation="vertical"
        android:layout_height="wrap_content"
        android:layout_margin="5dp">
    
        <ImageView
            android:id="@+id/fruit_image"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:background="#85C1E9"
            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>
    

    这里将宽度修改为了match_parent,因为瀑布流布局的宽度应该有布局的列数来自动计算.
    修改FruitAdapter.java中布局的引用

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

    修改MainActivity.java中的布局管理器部分,

    
    public class MainActivity extends AppCompatActivity {
        private List<Fruit> fruitList = new ArrayList<>(20);
    
        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();
        }
        /**
         * 自定义子布局初始化数据
         */
        private void initFruits(){
            Fruit fruit =null;
            String nameStr = "Cherry%s";
            StringBuilder drawableName = new StringBuilder("shuzi");
            for (int i = 1; i < 21; i++) {
                int resID = getResources().getIdentifier(drawableName.append(i).toString() , "drawable", getPackageName());
                fruit = new Fruit(getRandomLengthName(String.format(nameStr,i)),resID);
                fruitList.add(fruit);
                drawableName.setLength(5);
            }
        }
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            initFruits();
            RecyclerView recyclerView = findViewById(R.id.recycler_view);
    //        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
    //        linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
    //        recyclerView.setLayoutManager(linearLayoutManager);
            StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
            recyclerView.setLayoutManager(staggeredGridLayoutManager);
            FruitAdapter fruitAdapter = new FruitAdapter(fruitList);
            recyclerView.setAdapter(fruitAdapter);
        }
    }
    

    这里创建了一个StaggeredGridLayoutManager 的布局管理器,第一个参数3表示布局将分3列,第二个参数指定布局的排列方向.getRandomLengthName()方法用来创建一个随机数并修改Fruit的名字,将名字变得长短不一,来测试子项的高度.

    在测试一下GridLayoutManager,同样复制一下fruit_item.xml起名字为fruit_item_grid.xml,内容如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:orientation="vertical"
        android:layout_height="wrap_content"
        android:layout_margin="5dp">
    
        <ImageView
            android:id="@+id/fruit_image"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:background="#85C1E9"
            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>
    

    修改FruitAdapter.java中布局的引用

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

    修改MainActivity.java中的布局管理器部分,

    public class MainActivity extends AppCompatActivity {
       ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            initFruits();
            RecyclerView recyclerView = findViewById(R.id.recycler_view);
    //        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
    //        linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
    //        recyclerView.setLayoutManager(linearLayoutManager);
    //        StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
    //        recyclerView.setLayoutManager(staggeredGridLayoutManager);
            GridLayoutManager gridLayoutManager = new GridLayoutManager(MainActivity.this,4);
            recyclerView.setLayoutManager(gridLayoutManager);
            FruitAdapter fruitAdapter = new FruitAdapter(fruitList);
            recyclerView.setAdapter(fruitAdapter);
        }
    }
    

    这里的GridLayoutManager构造方法,根据API的描述,第一个参数为当前的Context,第二个参数为Grid的列数

    /**
         * Creates a vertical GridLayoutManager
         *
         * @param context Current context, will be used to access resources.
         * @param spanCount The number of columns in the grid
         */
        public GridLayoutManager(Context context, int spanCount) {
            super(context);
            setSpanCount(spanCount);
        }
    

    3.6.3 RecyclerView的点击事件

    在FruitAdapter.java中修改RecyclerView的注册点击事件,修改代码如下:

    public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
        private List<Fruit> mFruitList;
    
        static class ViewHolder extends RecyclerView.ViewHolder {
            View fruitView;
            ImageView fruitImage;
            TextView fruitText;
    
            public ViewHolder(@NonNull View itemView) {
                super(itemView);
                this.fruitView = itemView;
                fruitImage = itemView.findViewById(R.id.fruit_image);
                fruitText = itemView.findViewById(R.id.fruit_name);
            }
        }
    
        public FruitAdapter(List<Fruit> fruitList) {
            this.mFruitList = fruitList;
        }
    
        @NonNull
        @Override
        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, final int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item_grid, parent, false);
    //        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item_staggered, parent, false);
    //        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
    //        ViewHolder viewHolder = new ViewHolder(view);
            final ViewHolder viewHolder = new ViewHolder(view);
            viewHolder.fruitView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    int position = viewHolder.getAdapterPosition();
                    Fruit fruit = mFruitList.get(position);
                    Toast.makeText(view.getContext(), "you click view" + fruit.getName(), Toast.LENGTH_SHORT).show();
                }
            });
            viewHolder.fruitImage.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    int position = viewHolder.getAdapterPosition();
                    Fruit fruit = mFruitList.get(position);
                    Toast.makeText(view.getContext(),"You click Image"+fruit.getName(),Toast.LENGTH_SHORT).show();
                }
            });
            return viewHolder;
        }
    
        @Override
        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
            Fruit fruit = mFruitList.get(position);
            holder.fruitImage.setImageResource(fruit.getImageId());
            holder.fruitText.setText(fruit.getName());
        }
    
        @Override
        public int getItemCount() {
            return mFruitList.size();
        }
    }
    

    在内部类ViewHolder中增加了一个fruitVIew的变量来保存子组件的最外层实例,这样在监听点击动作时可以监听到最外层的组件.这里分别注册的最外层组件的点击事件及图片部分的点击事件.当用户点击不通区域时,则调用不同的事件.用户点击文字部分时,因为没有注册任何事件,则此事件被最外层布局捕捉到.

    3.7 编写界面的最佳实践

    这里需要先创建一个UIBestPractice的项目,修改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="wrap_content"
        android:background="@drawable/message_left">
    </LinearLayout>
    

    其中message_left如果使用普通的图片,则由于宽度问题图片可能被拉伸,此时的效果非常差,所以需要使用Android SDK自带的draw9patch来编辑图片

    3.7.1 制作Nine-Patch图片

    书中描述在Android Studio内置的jdk中有对应的工具,为Android Studio安装目录>/jre/bin,但测试时没有找到.将图片复制到idea中,选中图片,右键.此时有create 9-path file的选项,通过此功能也可以制作Nine-Patch图片.
    自己测试的几个图片,不太熟练.直接下载书中源码里图片继续测试.

    3.7.2 聊天界面

    聊天界面的例子主要分两个部分,一个是RecyclerVeiw实现的子组件来显示消息,一个是通过Text和Button组成的发送消息.修改activity_main.xml的布局文件如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#d8e0e8">
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/msg_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <EditText
                android:id="@+id/input_text"
                android:layout_width="0dp"
                android:layout_weight="1"
                android:hint="Type something  here"
                android:layout_height="wrap_content"
                android:maxLines="2"/>
            <Button
                android:id="@+id/send"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Send"/>
        </LinearLayout>
    </LinearLayout>
    

    创建一个实体类Msg来定义消息,主要属性有消息内容及消息类型(接收的消息和发送的消息),Msg.java如下

    
    public class Msg {
        public static final int TYPE_RECEIVED = 0;
        public static final int TYPE_SEND = 1;
        private String content;
        private int type;
    
        public Msg(String content, int type) {
            this.content = content;
            this.type = type;
        }
    	... setter and getter
    }
    

    根据RecyclerView的使用方式,需要一个Adapter和layoutManager.先创建一个MsgAdapter类,在类中实现ViewHolder的静态类及存放消息列表的属性,代码如下:

    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(@NonNull View view) {
                super(view);
                leftlayout = view.findViewById(R.id.left_layout);
                rightLayout = view.findViewById(R.id.right_layout);
                leftMsg = view.findViewById(R.id.left_msg);
                rightMsg = view.findViewById(R.id.right_msg);
            }
        }
    
        public MsgAdapter(List<Msg> mMsgList) {
            this.mMsgList = mMsgList;
        }
    
        @NonNull
        @Override
        public ViewHolder onCreateViewHolder(@NonNull 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(@NonNull 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 {
                holder.leftlayout.setVisibility(View.GONE);
                holder.rightLayout.setVisibility(View.VISIBLE);
                holder.rightMsg.setText(msg.getContent());
            }
    
        }
    
        @Override
        public int getItemCount() {
            return mMsgList.size();
        }
    
    }
    

    这里和上节中的例子基本一致.只是在onBindViewHolder()中增加了对消息类型的判断,如果是收到消息,则显示左侧消息布局,隐藏右侧.如果是发出的消息,就隐藏左侧显示右侧.现在修改MainActivity.java中的代码,

    public class MainActivity extends AppCompatActivity {
    
        private RecyclerView recyclerView;
        private EditText inputText;
        private Button sendButton;
        private MsgAdapter msgAdapter;
        private List<Msg> msgList = new ArrayList<>();
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            recyclerView = findViewById(R.id.msg_recycler_view);
            inputText = findViewById(R.id.input_text);
            sendButton = findViewById(R.id.send);
    
            LinearLayoutManager layoutManager = new LinearLayoutManager(this);
            recyclerView.setLayoutManager(layoutManager);
            msgAdapter = new MsgAdapter(msgList);
            recyclerView.setAdapter(msgAdapter);
    
            sendButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    String content = inputText.getText().toString();
                    if (!"".equals(content)) {
                        Msg msg = new Msg(content, getRandomType());
                        msgList.add(msg);
    //                    当有新消息时刷新ListView中的显示
                        msgAdapter.notifyItemInserted(msgList.size() - 1);
    //                    将ListView定位到最后一行
                        recyclerView.scrollToPosition(msgList.size() - 1);
                        inputText.setText("");
                    }
                }
            });
        }
        //随机获取消息类型
        private int getRandomType() {
            Random random = new Random();
            if (random.nextInt(10) > 5) {
                return Msg.TYPE_SEND;
            } else {
                return Msg.TYPE_RECEIVED;
            }
        }
    }
    

    这里增加了一个getRandomType的方法,用来随机生成消息是发送的还是接收的.

    遗留的问题

    • 笔记3.5.4 ListView的点击事件中,子布局中子组件点击事件加载顺序及是否会覆盖的问题
  • 相关阅读:
    练字的感悟
    关于简单
    全都是泡沫
    跟着电影环游世界
    12.8《印度之行》
    11.21派生类对基类的访问
    Linux,begin
    如何利用google
    回调函数
    原型对象
  • 原文地址:https://www.cnblogs.com/GYoungBean/p/13307457.html
Copyright © 2011-2022 走看看