Android开发3:UI开发
#以下所有学习内容参考郭霖编写的《Android第一行代码》
按照书上的进度进行学习,该贴只是记录本人学习的笔记。
一、常用控件
1.TextView(文本)
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="24sp"
android:textColor="#00ff00"
android:text="This is TextView."
/>
2.Button(按钮)
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="Button"/>
????叠在一起了
好吧要改成
3.EditText(文本输入编辑)
<EditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入内容"
android:maxLines="2"/>
通过点击按钮来获取EditText中输入的内容:
package com.example.ui;
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 button=(Button)findViewById(R.id.button);
editText=(EditText)findViewById(R.id.edit_text);
button.setOnClickListener(this);
}
@Override
public void onClick(View v){
switch(v.getId()){
case R.id.button:
String inputText=editText.getText().toString();
Toast.makeText(MainActivity.this,inputText, Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
}
4.ImageView(图片)
<ImageView
android:id="@+id/image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/img_1"/>
通过button动态更改图片:
package com.example.ui;
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;
import android.widget.ImageView;
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=(Button)findViewById(R.id.button);
editText=(EditText)findViewById(R.id.edit_text);
imageView=(ImageView)findViewById(R.id.image_view);
button.setOnClickListener(this);
}
@Override
public void onClick(View v){
switch(v.getId()){
case R.id.button:
//修改图片(src)
imageView.setImageResource(R.drawable.img_2);
break;
default:
break;
}
}
}
运行:
点击按钮:
5.ProgressBar(进度条)
<ProgressBar
android:id="@+id/process_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
注意:所有Android控件都具有andriod:visibility属性,可取visible、invisible、gone。visible表示控件是可见的,invisible表示不可见,但是它仍然占据原来的位置的大小,gone不可见,而且不在占用任何屏幕空间。
还可以通过使用setVisibility(),参数传入View.VISIBLE、View.INVISIBLE和View.GONE来改变可见性。
现在尝试通过点击按钮使进度条消失又出现:
package com.example.ui;
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.ProgressBar;
import android.widget.Toast;
import android.widget.ImageView;
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=(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);
button.setOnClickListener(this);
}
@Override
public void onClick(View v){
switch(v.getId()){
case R.id.button:
if(progressBar.getVisibility()==View.GONE){
progressBar.setVisibility(View.VISIBLE);
}
else{
progressBar.setVisibility(View.GONE);
}
break;
default:
break;
}
}
}
运行:
按下按钮:
还可以设置水平进度条:
<ProgressBar
android:id="@+id/process_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"
android:max="100"/>
通过按钮修改进度条的值:
@Override
public void onClick(View v){
switch(v.getId()){
case R.id.button:
int progress=progressBar.getProgress();
progress=progress+10;
progressBar.setProgress(progress);
break;
default:
break;
}
}
运行:
6.AlertDialog(弹出对话框)
AlertDialog可以在当前的界面弹出一个对话框,这个对话框置顶于所有界面元素之上,能够屏蔽掉其他控件的交互能力。
@Override
public void onClick(View v){
switch(v.getId()){
case R.id.button:
//创建AlertDialog实例
AlertDialog.Builder dialog=new AlertDialog.Builder(MainActivity.this);
//设置标题,内容,可否用Back键关闭对话框
dialog.setTitle("This is dialog.");
dialog.setMessage("Something important.");
dialog.setCancelable(false);
//设置确定按钮的点击事件
dialog.setPositiveButton("OK",new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface dialog,int which){
}
});
//设置取消按钮的点击事件
dialog.setNegativeButton("Cancel",new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface dialog,int which){
}
});
//将对话框显示出来
dialog.show();
break;
default:
break;
}
}
运行:
7.ProgressDialog(进度条对话框)
ProgressDialog和AlertDialog类似可以弹出对话框并屏蔽掉其他控件的交互能力,同时会在对话框中显示一个进度条。
@Override
public void onClick(View v){
switch(v.getId()){
case R.id.button:
ProgressDialog progressDialog =new ProgressDialog(MainActivity.this);
progressDialog.setTitle("This is ProgressDialog");
progressDialog.setMessage("Loading...");
progressDialog.setCancelable(true);
progressDialog.show();
break;
default:
break;
}
}
运行:
二、4种基本布局
1.线性布局(LinearLayout)
LinearLayout又称作线性布局,是一种非常常用的布局。这个布局会将它所包含的控件在线性方向上(vertical、horizontal)依次排列。
知识点1:android:orientation可以指定控件的排列方向,可取vertical和horizontal。注意取vertical时,内部的控件就不能将高度指定为match_parent。同理,取horizontal时,内部的控件不能将宽度指定为match_parent。
知识点2: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:id="@+id/button_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:text="Button 1"
/>
<Button
android:id="@+id/button_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="Button 2"/>
<Button
android:id="@+id/button_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="Button 3"/>
</LinearLayout>
效果如图:
知识点3: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:id="@+id/input_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Type something..."/>
<Button
android:id="@+id/send"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Send"/>
</LinearLayout>
在EditText和Button中都将android:layout_weight属性的值设定为1,这表示EditText和Button将在水平方向平分宽度。可以看作是每个控件所占的比例。
效果如图:
2.相对布局(RelativeLayout)
RelativeLayout又称作相对布局,也是一种非常常用的布局。它可以通过相对定位的方式让控件出现在布局的任何位置。
知识点1:相对于父布局进行定位
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:andriod="http://schemas.android.com/apk/res/android"
andriod:layout_width="match_parent"
andriod:layout_height="match_parent">
<Button
andriod:id="@+id/button1"
andriod:layout_width="wrap_content"
andriod:layout_height="wrap_content"
andriod:layout_alignParentLeft="true"
andriod:layout_alignParentTop="true"
andriod:text="Button 1"/>
<Button
andriod:id="@+id/button2"
andriod:layout_width="wrap_content"
andriod:layout_height="wrap_content"
andriod:layout_alignParentRight="true"
andriod:layout_alignParentTop="true"
andriod:text="Button 2"/>
<Button
andriod:id="@+id/button3"
andriod:layout_width="wrap_content"
andriod:layout_height="wrap_content"
andriod:layout_centerInParent="true"
andriod:text="Button 3"/>
<Button
andriod:id="@+id/button4"
andriod:layout_width="wrap_content"
andriod:layout_height="wrap_content"
andriod:layout_alignParentLeft="true"
andriod:layout_alignParentBottom="true"
andriod:text="Button 4"/>
<Button
andriod:id="@+id/button5"
andriod:layout_width="wrap_content"
andriod:layout_height="wrap_content"
andriod:layout_alignParentRight="true"
andriod:layout_alignParentBottom="true"
andriod:text="Button 5"/>
</RelativeLayout>
效果如图:
知识点2:相对于控件进行定位
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:andriod="http://schemas.android.com/apk/res/android"
andriod:layout_width="match_parent"
andriod:layout_height="match_parent">
<Button
andriod:id="@+id/button3"
andriod:layout_width="wrap_content"
andriod:layout_height="wrap_content"
andriod:layout_centerInParent="true"
andriod:text="Button 3"/>
<Button
andriod:id="@+id/button1"
andriod:layout_width="wrap_content"
andriod:layout_height="wrap_content"
andriod:layout_above="@id/button3"
andriod:layout_toLeftOf="@id/button3"
andriod:text="Button 1"/>
<Button
andriod:id="@+id/button2"
andriod:layout_width="wrap_content"
andriod:layout_height="wrap_content"
andriod:layout_above="@id/button3"
andriod:layout_toRightOf="@id/button3"
andriod:text="Button 2"/>
<Button
andriod:id="@+id/button4"
andriod:layout_width="wrap_content"
andriod:layout_height="wrap_content"
andriod:layout_below="@id/button3"
andriod:layout_toLeftOf="@id/button3"
andriod:text="Button 4"/>
<Button
andriod:id="@+id/button5"
andriod:layout_width="wrap_content"
andriod:layout_height="wrap_content"
andriod:layout_below="@id/button3"
andriod:layout_toRightOf="@id/button3"
andriod:text="Button 5"/>
</RelativeLayout>
注意button3(被后面引用)要写在前面,否则会报错。
效果如图:
3.帧布局(FrameLayout)
这种布局没有方便的定位方式,所有控件都会默认摆放在布局的左上角。如下:
可以使用layout_gravity属性来指定控件在布局中的对齐方式。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:text="This is TextView"/>
<ImageView
android:id="@+id/image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:src="@mipmap/ic_launcher"/>
</FrameLayout>
效果如图:
4.百分比布局(PercentFrameLayout和PercentRelativeLayout)
在百分比布局中,我们不再使用wrap_content和match_parent等方式来指定控件的大小,而是允许直接指定控件在布局中所占的百分比,这样的话就可以轻松实现平分布局甚至是任意比例分割布局的效果了。
由于LinearLayout本身已经支持按比例指定控件的大小了,因此百分比布局只为FrameLayout和RelativeLayout进行了功能扩展,提供了PercentFrameLayout和PercentRelativeLayout这两个全新的布局。
首先要在项目的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="40%"/>
<Button
android:id="@+id/button2"
android:text="Button 2"
android:layout_gravity="right|top"
app:layout_widthPercent="50%"
app:layout_heightPercent="50%"/>
<Button
android:id="@+id/button3"
android:text="Button 3"
android:layout_gravity="left|bottom"
app:layout_widthPercent="50%"
app:layout_heightPercent="50%"/>
<Button
android:id="@+id/button4"
android:text="Button 4"
android:layout_gravity="right|bottom"
app:layout_widthPercent="30%"
app:layout_heightPercent="50%"/>
</android.support.percent.PercentFrameLayout>
效果如图:
三、自定义控件
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="返回"
android:textColor="#0000FF"/>
<TextView
android:id="@+id/title_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:gravity="center"
android:text="标题栏"
android:textColor="#FF1493"
android:textSize="24sp"
/>
<Button
android:id="@+id/title_edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:text="编辑"
android:textColor="#0000FF"/>
</LinearLayout>
在activity_main.xml 中引入这个布局:
<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>
再把原来默认的标题栏隐藏掉:
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();
}
}
}
效果如图:
2.自定义控件(可复用)
如果只是使用可复用布局,只能复用其样式设计,不能复用里面按钮的点击事件,因此在这里我们引入自定义控件,可以把整个布局包括其中的点击事件都复用出来。
首先,新建一个自定义控件:TitleLayout(java类),它继承自LinearLayout:
这里还定义了两个按钮的点击事件:
public class TitleLayout extends LinearLayout{
//构造函数
public TitleLayout(Context context, AttributeSet attrs){
super(context,attrs);
//动态加载布局文件
//通过from创建一个LayoutInflater对象,利用inflate动态加载布局文件
//inflate第一个参数:要加载的布局文件的id,第二个参数:添加一个父布局
LayoutInflater.from(context).inflate(R.layout.title,this);
Button titleBack=(Button)findViewById(R.id.title_back);
Button titleEdit=(Button)findViewById(R.id.title_edit);
//销毁当前活动
titleBack.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
((Activity)getContext()).finish();
}
});
//弹出一段文本
titleEdit.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(),"you clicked Edit button",Toast.LENGTH_SHORT).show();
}
});
}
}
在activity_main.xml 中引入这个布局:
添加自定义控件和添加普通控件的方式基本是一样的,只不过在添加自定义控件的时候,我们就需要指明控件的完整类名,包名在这里是不可以省略的。
<?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">
<com.example.uicustomviews.TitleLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
效果如图:
四、最常用和最难用的控件——ListView
当我们的程序中有大量数据要展示的时候,就可以借助ListView来实现。ListView允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据则会滚出屏幕。
1.ListView简单用法
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
MainActivity:
package com.example.listviewtest;
import android.support.v7.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","Blueberry","Strawberry","Cherry","Mango",
"Apple","Banana","Orange","Watermelon","Pear","Grape",
"Pineapple","Blueberry","Strawberry","Cherry","Mango"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//数组中的数据是无法直接传递给ListView的,需要借助适配器来完成
//提供的数据类型是字符串,因此将ArrayAdapter的繁星指定为<String>
//构造函数,第一个参数:当前上下文;第二个参数:ListView子项布局的id;第三个参数:要适配的数据
// android.R.layout.simple_list_item_1是Android内置的布局文件
ArrayAdapter<String>adapter=new ArrayAdapter<String>(
MainActivity.this,android.R.layout.simple_list_item_1,data);
ListView listView =(ListView)findViewById(R.id.list_view);
//将构建好的适配器对象传递进去
listView.setAdapter(adapter);
}
}
效果如图:
2.定制ListView界面
打算展示一个这样的界面:
首先建一个Fruit实体类:
public class Fruit {
private String name;
private int imageId;
public Fruit(String name,int imageId){
this.name=name;
this.imageId=imageId;
}
public String getName(){
return name;
}
public int getImageId(){
return imageId;
}
}
自定义一个布局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="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_marginLeft="10dp"/>
</LinearLayout>
自定义一个适配器FruitAdapter:
package com.example.listviewtest;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import java.util.List;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
/**
* Created by ThinkPad on 2020/10/12.
*/
public class FruitAdapter extends ArrayAdapter<Fruit>{
private int resourceId;
//构造函数,将上下文、ListView子布局id和数据传递进来
public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects){
super(context,textViewResourceId,objects);
resourceId=textViewResourceId;
}
//重写getView(),这个方法在每个子项被滚动到屏幕内的时候会被调用
@Override
public View getView(int position, View convertView, ViewGroup parent){
//获取当前项的Fruit实例
Fruit fruit=getItem(position);
//布局也是一个View
//第一个参数:想要加载的布局资源id
//第二个参数:为布局添加父布局
//第三个参数:指定成false,表示只让我们在父布局中声明的layout属性生效,但不会为这个View添加父布局
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;
}
}
修改MainActivity代码:
package com.example.listviewtest;
import android.support.v7.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<>();
@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);
}
protected 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 Mengo=new Fruit("Mengo",R.drawable.mengo_pic);
fruitList.add(Mengo);
}
}
}
最终就可以得到上述的效果。
3.提高ListView的效率
getView()方法每次都将布局重新加载了一次,当ListView快速滚动时,这就会成为性能的瓶颈。
利用convertView来缓存之前加载好的布局:
public class FruitAdapter extends ArrayAdapter<Fruit>{
private int resourceId;
//构造函数,将上下文、ListView子布局id和数据传递进来
public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects){
super(context,textViewResourceId,objects);
resourceId=textViewResourceId;
}
//重写getView(),这个方法在每个子项被滚动到屏幕内的时候会被调用
@Override
public View getView(int position, View convertView, ViewGroup parent){
//获取当前项的Fruit实例
Fruit fruit=getItem(position);
View view;
if(convertView==null){
view= LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
}
else{
view=convertView;
}
ImageView fruitImage=(ImageView)view.findViewById(R.id.fruit_image);
TextView fruitName=(TextView)view.findViewById(R.id.fruit_name);
fruitImage.setImageResource(fruit.getImageId());
fruitName.setText(fruit.getName());
return view;
}
}
但此时,还是每次都调用findViewById()这个方法来获取一次控件的实例。因此,我们用新增的内部类ViewHolder来对控件的实例进行缓存:
public class FruitAdapter extends ArrayAdapter<Fruit>{
private int resourceId;
//构造函数,将上下文、ListView子布局id和数据传递进来
public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects){
super(context,textViewResourceId,objects);
resourceId=textViewResourceId;
}
//重写getView(),这个方法在每个子项被滚动到屏幕内的时候会被调用
@Override
public View getView(int position, View convertView, ViewGroup parent){
//获取当前项的Fruit实例
Fruit fruit=getItem(position);
View view;
ViewHolder viewHolder;
if(convertView==null){
view= LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
viewHolder=new ViewHolder();
//把控件实例都存放在viewHolder里
viewHolder.fruitImage=(ImageView)view.findViewById(R.id.fruit_image);
viewHolder.fruitName=(TextView)view.findViewById(R.id.fruit_name);
//把viewHolder对象存储在View中
view.setTag(viewHolder);
}
else{
view=convertView;
//取出ViewHolder对象
viewHolder=(ViewHolder)view.getTag();
}
viewHolder.fruitImage.setImageResource(fruit.getImageId());
viewHolder.fruitName.setText(fruit.getName());
return view;
}
class ViewHolder{
ImageView fruitImage;
TextView fruitName;
}
}
经过两步的优化之后,现在ListView的运行效率已经非常不错了。
4.ListView的点击事件
package com.example.listviewtest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList=new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化水果数据
initFruits();
FruitAdapter adapter=new FruitAdapter(MainActivity.this,R.layout.fruit_item,fruitList);
ListView listView =(ListView)findViewById(R.id.list_view);
//将构建好的适配器对象传递进去
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener(){
@Override
public void onItemClick(AdapterView<?> parent,View view,int position,long id){
Fruit fruit=fruitList.get(position);
Toast.makeText(MainActivity.this,fruit.getName(),Toast.LENGTH_SHORT).show();
}
});
}
protected 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 Mengo=new Fruit("Mengo",R.drawable.mengo_pic);
fruitList.add(Mengo);
}
}
}
效果如图:
五、更强大的滚动控件——RecyclerView
RecyclerView是一个增强版的ListView, 不仅可以轻松实现和ListView同样的效果,还优化了ListView中存在的各种不足之处(性能问题和不能横向滚动)。目前Android官方更推荐使用RecyclerView,未来也会有更多的程序逐渐从ListView转向RecyclerView。
1.RecyclerView的基本用法
Android将RecyclerView定义在了support库当中,因此首先要在build.gradle中添加依赖:
在activity_main.xml中加入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">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
Fruit类和图片使用ListView中的。
新建FruitAdapter类:
package com.example.recyclerview;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.view.View;
import java.util.List;
/**
* Created by ThinkPad on 2020/10/14.
*/
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder>{
private List<Fruit> mFruitList;
static class ViewHolder extends RecyclerView.ViewHolder{
ImageView fruitImage;
TextView fruitName;
//构造函数
//要传入View参数,通常就是RecyclerView子项的最外层布局
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;
}
//用于创建ViewHolder实例
@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;
}
//对RecyclerView子项数据进行赋值,会在每个子项被滚动到屏幕内时被执行
//通过position得到当前项的Fruit实例,然后再将数据设置到ViewHolder的ImageView和TextView中
@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.recyclerview;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import java.util.List;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList=new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化水果数据
initFruits();
//获取RecyclerView实例
RecyclerView recyclerView=(RecyclerView)findViewById(R.id.recycler_view);
//创建LinearLayoutManager对象,并指定给recyclerView为线性布局
LinearLayoutManager layoutManager=new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
//创建FruitAdapter实例,并传入数据,调用setAdapter()完成配置器设置
FruitAdapter adapter=new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
}
//水果数据初始化
protected 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 Mengo=new Fruit("Mengo",R.drawable.mengo_pic);
fruitList.add(Mengo);
}
}
}
效果如图:
2.实现横向滚动和瀑布流布局
1.横向滚动
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="100dp"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/fruit_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"/>
<TextView
android:id="@+id/fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"/>
</LinearLayout>
MainActivity(设置布局横向排列)
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
效果:
2.瀑布流布局
上面的LinearLayoutManager是线性布局,而StaggeredGridLayoutManager可用于实现瀑布流布局。
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.recyclerview;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import java.util.List;
import java.util.ArrayList;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList=new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化水果数据
initFruits();
//获取RecyclerView实例
RecyclerView recyclerView=(RecyclerView)findViewById(R.id.recycler_view);
//设置瀑布流布局
//第一个参数:指定布局的列数 第二个参数:指定布局的排列方向
StaggeredGridLayoutManager layoutManager=new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
//创建FruitAdapter实例,并传入数据,调用setAdapter()完成配置器设置
FruitAdapter adapter=new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
}
//水果数据初始化
protected 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 Mengo=new Fruit(getRandomLengthName("Mengo"),R.drawable.mengo_pic);
fruitList.add(Mengo);
}
}
private String getRandomLengthName(String name){
Random random=new Random();
//1~20随机数
int length=random.nextInt(20)+1;
StringBuilder builder=new StringBuilder();
for(int i=0;i<length;i++){
builder.append(name);
}
return builder.toString();
}
}
效果如图:
3.RecyclerView的点击事件
RecyclerView并没有类似于setOnItemClickListener()这样的注册监听器方法,而是需要给子项具体的View注册点击事件。
修改FruitAdapter:
package com.example.recyclerview;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.view.View;
import android.widget.Toast;
import java.util.List;
/**
* Created by ThinkPad on 2020/10/14.
*/
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder>{
private List<Fruit> mFruitList;
static class ViewHolder extends RecyclerView.ViewHolder{
//保存子项最外层布局的实例
View fruitView;
ImageView fruitImage;
TextView fruitName;
//构造函数
//要传入View参数,通常就是RecyclerView子项的最外层布局
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;
}
//用于创建ViewHolder实例
@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);
//整个View的点击事件
holder.fruitView.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
int position=holder.getAdapterPosition();
Fruit fruit=mFruitList.get(position);
Toast.makeText(v.getContext(),"you clicked view"+fruit.getName(),Toast.LENGTH_SHORT).show();
}
});
//图片的点击事件,还可以对文字也单独设置
holder.fruitImage.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
int position=holder.getAdapterPosition();
Fruit fruit=mFruitList.get(position);
Toast.makeText(v.getContext(),"you clicked image"+fruit.getName(),Toast.LENGTH_SHORT).show();
}
});
return holder;
}
//对RecyclerView子项数据进行赋值,会在每个子项被滚动到屏幕内时被执行
//通过position得到当前项的Fruit实例,然后再将数据设置到ViewHolder的ImageView和TextView中
@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();
}
}
效果如图:
最后的最后,完成了一个聊天框!!照着书上练习的
开心!!!!!!