====================================
====== 第三章:UI开发的点点滴滴 ======
====================================
界面设计和功能实现同样重要。界面美观可以增加用户粘性。
1、如何编写程序界面。
我们不建议使用可视化见面来进行拖拽编写布局,因为可视化编辑不利于你去真正了解界面背后的实现原理。通过可视化界面写出的界面通常不具备很好的屏幕适配性。并且编写复杂界面时无法胜任。因此本书的所有界面都是通过编写xml代码来实现的。
2、重用控件的使用方法:
新建一个UIWidgetTest项目。
2.1 TextView:听说是最简单的控件,显示一段文本信息。修改activity_main.xml中代码如下:
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:orientation=“vertical”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<TextView
android:id=“@+id/text_view”
android:layout_width=“match_parent”
android:layout_height=“wrap_context”
android:text=“This is TextView” />
</LinearLayout>
match_parent、fill_parent、wrap_content都是控件的可选属性。其中,match_parant和fill_parent的意义相同,官方更加推荐match_parent。wrap_context表示让当前控件的大小刚好可以包住里面的内容,也就是控件内容决定当前控件的大小。当然你也可以指定控件一个特定值的宽高的大小。(但是这样可能会出现屏幕的适配问题)
如需修改textview里面的文字属性,增加android:textSize=“24sp” android:textColor=“#00ff00”
Android中字体大小使用sp作为单位。
3.2.2 Button
添加一个Button,
<Button
android:id=“@+id/button”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
andoird:text=“Button” />
我们在布局文件里面设置的文字是Button,但是最终显示结果却是BUTTON,这是由于系统会对button中的所有英文字母自动进行大写转换,如果这不是想要的效果,可以使用如下配置来禁用这一默认特新:Android:textAllCaps=“false”
接着,在MainActivity中添加按钮的点击逻辑
Public class MainActivity extends AppCpmpatActivity {
@ovrride
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.onClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button: {
// 点击按钮的逻辑
}break;
default:
break;
}
}
});
}
}
如果不喜欢使用匿名类的方式来注册监听器,也可以使用实现接口的方式来进行注册
Public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch(v.getId()) {
case R.id.button : {
// 点击逻辑
}break;
default:
break;
}
}
3.2.3 EditText(输入框)
一样的,android:hint=“placeholder text”, android:maxLines=“2”
3.2.4 ImageView
显示图片的控件,图片通常都是放在drawable开头的目录下
创建一个drawable-xhdpi的文件夹,放入两张图片
<ImageView
android:id=“@+id/image_view”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:src=“@drawable/img_1” />
3.2.5 ProcessBar(中间圆圈进度旋转)
<ProcessBar
android:id=“@+id/progress_bar”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
style=“?android:attr/progressBarStyleHorizontal”
android:max=“100” />
所有控件都可以设置一个属性visibility,可选值为visible可见的(默认值)、invisible不可见和gone不仅不可见而且不占任何屏幕控件。我们还可以通过代码来设置控件的可见性,使用的是视图的setVisibility(),可以传入View.VISIBLE、View.INVISIBLE、View.GONE
if (progressBar.getVisibility() == View.GONE) {
progressBar.setVisibility(View.VISIBLE);
} else {
progressBar.setVisibility(View.GONE);
}
// 当修改了progressBar的样式之后,修改如下代码,就可以点击button的时候让progress一点点增加
int progress = progressBar.getProgress();
progress += 10;
progressBar.setProgress(progress);
3.2.6 AlertDialog
AlertDialog可以在当前界面弹出一个对话框,并且是置顶于所有控件之上,能够屏蔽掉其他控件的交互能力,因此AlertDialog一般都是用于提示一些非常重要的内容或者警告信息。
我们现在可以在MainActivity中写上如下代码。
public void onClick(View v) {
switch (v.getId()) {
case R.id.button: {
AlertDialog.Bulder dialog = new AlertDialog.Bulder(MainActivity.this);
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;
}
}
}
setPositiveButton()方法为对话框设置确定按钮点击事件,setNegativeButton()方法设置取消按钮点击事件。最后调用show()方法显示出来,
3.2.7 ProgressDialog(貌似现在已经取消使用了)
与AlertDialog类似,但是会有一个进度条,一般表示当前操作比较耗时,让用户耐心等待
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
…
@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(ture);
progressDialog.show();
}break;
default:
break;
}
}
设置setCancelable()传入false,表示不能通过back键取消掉。
3.3 详解4种基本布局。(新的测试项目UILayoutTest)
布局的内部除了放置控件外,还可以放置布局,通过多层布局的嵌套,我们就能够完成一些比较复杂的界面实现。
Android中包括四种布局:线性布局、相对布局、帧布局、百分比布局
3.3.1 线性布局
线性布局LinearLayout,将他所包含的控件在线性方向上依次排列。之前我们用的一直都是这个线性布局,可以通过修改adroid:orientation属性指定排列方向是vertival为垂直方向,horizontal为水平方向。
<LinearLayout xmlns:android:=“http://schemas.android.com/apk/res/android”
android:orientation=“vertical”/horizontal
android:layout_width=“match_parent”
android:layout_height=“match_parent” >
</ LinearLayout>
需要注意的是,如果排列方式设置为horizontal水平方向,内部的控件就绝对不能将宽度指定为match_parent,因为这样的话,单独一个控件就占满整个水平方向了,其他控件就没有位置可以放置了。同样道理,如果排列方向设置为vertival垂直方向,那么内部控件的高度就不能设置为match_parent。
android:layout_gravity属性用于指定文字在控件中的对齐方式。
Android:layout_weight使用比例的方式来指定控件的大小(在手机屏幕的适配性方便非常重要)
案例:
<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” />
这里虽然设置了android:layout_width=“0dp”,但是由于设置了比重,以比重为准,0dp是比较标准的写法。
比重就是总数加起来,然后再根据占的比重来决定宽度,现在很明显是EditText和Button平分宽度。
这时候如果Button的layout_width改为wrap_content,然后EditText保持1的比比重不变,那么Button就会保持固定大小,然后EditText占据剩余的宽度空间。
3.3.2 相对布局
相对布局RelativeLayout,相对随意,它可以通过相对定位的方式让控件出现在布局的任何位置。因此,属性非常多,不过这些属性都是有规律可循。
如案例:5个按钮,分别在左上,右上,中间,左下,右下。
<RelativeLayout xmlns:android:”http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent” >
<Button
android:id=“@+id/button1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentLeft=“true”
android:layout_alignParentTop=“true”
android:text=“Button 1” />
<Button
android:id=“@+id/button2”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentRight=“true”
android:layout_alignParentTop=“true”
android:text=“Button 2” />
<Button
android:id=“@+id/button3”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_centerInParent=“true”
android:text=“Button 3” />
<Button
android:id=“@+id/button4”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentLeft=“true”
android:layout_alignParentBottom=“true”
android:text=“Button 4” />
<Button
android:id=“@+id/button5”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentRight=“true”
android:layout_alignParentBottom=“true”
android:text=“Button 5” />
</RelativeLayout>
上面是相对于父控件进行定位的。
如果使用相对于其他控件的相对布局,用下面的方式
android:layout_above=“@id/button3” 上方
android:layout_toRightOf=“@id/button3” 右侧
android:layout_toLeftOf=“@id/button3” 左侧
android:layout_below=“@id/button3” 下侧
android:layout_alignLeft 让一个控件的左边缘和另一个控件的左边缘对齐
android:layout_alignRight 让一个控件的右边缘和另一个控件的右边缘对齐
android:layout_alignTop 上边缘对齐
android:layout_alignBottom 下边缘对齐
3.3.3 帧布局
帧布局FrameLayout,相对其他两种布局简单多了,应用场景也少了很多。
这种布局没有方便的定位方式,所有的控件都会默认摆放在布局的左上角
<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:text=“This is TextView” />
<ImageView
android:id=“@+id/image_view”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:src=“@mipmap/ic_launcher” />
</FrameLayout>
FrameLayout还可以使用layout_gravity属性
Android:layout_gravity=“left”
Android:layout_gravity=“right”
由于FrameLayout的定位方式的缺失,导致它的使用场景比较少。
3.3.4 百分比布局
前面3种布局都是从android 1.0版本就开始支持了,一直沿用到现在。这种布局中,我们可以不再使用wrap_content、match_parent等方式来指定控件的大小,而是允许直接指定控件在布局中所占的百分比。
百分比布局只为FrameLayout和RelativeLayout进行了功能拓展,提供了PercentFrameLayout和PercentRelativeLayout这两种全新的布局。
怎样做到新增的百分比布局在所有的android版本中都能使用呢。为此,android团队将百分比布局定义在support库当中。我们只需要在项目中build.gradle中添加百分比布局库的以来,就能保证百分比布局在android所有系统版本上的兼容性了。
修改app/build.gradle文件中
Dependencies {
implementation ‘com.android.support:percent:28.0.0’
}
如果发现28.0.0版本不够新,系统会提示你修改。修改完之后还需要重新build一下。(点击Sync Now)
现在使用PercentFrameLayout
app:layout_widthPercent=“50%” // 将宽度指定为布局的50%
app:layout_heightPercent=“50%” // 将高度指定为布局的50%
PercentFrameLayout还是会继承FrameLayout的特性,即所有的控件默认都摆放在布局的左上角,为了让四个按钮不会重叠,还是接住了layout_gravity分别将4个按钮放置在左上、右上、左下、右下四个位置。android:layout_gravity="left|bottom"
PercentRelativeLayout的用法类似,也是继承了RelativeLayout的所有属性,然后再结合app:layout_widthPercent和app:layout_heightPercent来按百分比指定控件的宽高
3.4 创建自定义控件
所有控件都是继承自View,所有的布局都是直接或者间接继承自ViewGroup的。
创建一个新的应用UICustomViews
单独创建一个title.xml,用于设定类似iOS的顶部导航栏,然后在activity_main.xml中添加<include layout=“@layout/title” />
android:background=“@drawable/title_bg.png” // 用于指定背景图片
Android:layout_marginLeft,layout_marginRight,layout_mariginTop,layout_marginBottom用于指定上下左右偏移的距离
android:background可以指定一个背景图片.
android:layout_margin属性可以指定控件在上下左右方向上偏移的距离,当然也可以使用
android:layout_marginLeft或者android:layout_marginTop等属性来单独指定控件在某个方向上便宜的距离。
接下来修改activity_main.xml
加上<include layout=“@layout/title” />
最后在MainActivity中将系统自带的标题栏隐藏掉
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();
}
}
}
ActionBar就是我们的导航栏
隐藏标题栏之后,我们自己写的tilte.xml就生效了。以后只要一句include就可以包含进来了。
3.4.2 创建自定义控件
新建TitleLayout继承自LinearLayout,让它成为我们自定义的标题栏控件
public class TitleLayout extends LinearLayout {
public TitleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.title, this);
}
}
在布局中引入TitleLayout控件就会调用这个构造函数,然后在构造函数中需要对标题栏布局进行动态加载,这就要借助LayoutInflater来实现。通过LayoutInflater的from()方法可以构建出一个ayoutInflater对象,然后调用inflate()方法就可以动态加载一个布局文件,inflate()方法两个参数,第一个是加载的布局文件的id,这里传入R.layout.title,第二个参数是给加载好的布局再添加一个父布局,这里我们想要指定为TitleLayout,于是直接传入this。
现在这个自定义控件创建好了,然后我们需要在布局文件中添加这个自定义控件,修改activity_main.xml中的代码,如下所示:
<LinearLayout xmlns:android=“http”//schemas.android.com/apk/res/android”
android:layout_width=“match_parant”
android:layout_height=“match_parent” >
// 包名在这里是不可以省略的
<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, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.title, this);
Button titleBack = (Button) findViewById(R.id.title_back);
Button titleEdit = (Button) findViewById(R.id.title_edit);
titleBack.setOnclickListener(new OnClickListener() {
@Override
public void onClick(View v) {
((Activity) getContext()).finish();
}
});
titleEdit.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(), “You click Edit button”, Toast.LENGTH_SHORT).show();
}
});
}
}
首先还是通过findViewById()方法得到按钮的实例,然后分别调用setOnclickListenner()方法给两个按钮注册了点击事件,
当点击返回按钮的时候销毁当前的活动
当点击编辑按钮的时候弹出一段文本。
3.5 最常用和最难用的空控件 —》 ListView(相当于iOS中的UITableView)
3.5.1 LIstView的简单用法
新建一个项目ListViewTest项目,然后修改activity_main.xml中的代码
<LinearLayout xmlns:android=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的代码
public class MainActivity extends AppCompatActivity {
private String[] data = {
“Apple”,”Banana”,”Orange”,”watermelon”,”Pear”,”Grape”,”Pineapple”,”Strawberry”,”Cherry”,”Mango”,
“Apple”,”Banana”,”Orange”,”watermelon”,”Pear”,”Grape”,”Pineapple”,”Strawberry”,”Cherry”,”Mango”
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
MainActivity.this, android.R.layout.simple_list_item_1, data);
ListView listView = (ListView) findViewById(R.id.list_view);
listView.setAdapter(adapter);
}
}
我们需要通过借助适配器来把数据传递给ListView,Android中提供了很多适配器的实现类,其中我认为最好用的就是ArrayAdapter。它可以通过泛型来指定要适配的数据类型,然后在构造函数中把要适配的数据传入。ArrayAdater有多个构造函数的重载,你应该根据实际情况选择最合适的一种。上面在ArrayAdapter的构造函数中依次穿日当前上下文、ListView子项布局的id,以及要适配的数据。注意,我们使用了android.R.layout.simple_list_item_1作为子项布局的id,这是一个Android内置的布局文件,里面只有一个TextView,可用于简单的显示一段文本。
3.5.2 定制ListView的界面
只显示一段文本的LIstView太单调了,现在我们来对ListView的界面进行定制。
首先我们需要准备好一组图片,分别对应提供的每一种水果。
接着定义一个实体类,作为ListView适配器的适配类型。新建Fruit类,代码如下:
public class Fruit {
private String name;
private int imageId;
public Fruit(String name, int imageId) {
this.name = name; // 水果名
this.imageId = imageId; // 水果资源id
}
public String getName() {
return name;
}
public int getImageId() {
return imageId;
}
}
然后我们为ListVie的子项指定一个我们自定义的布局,在layout目录下新建fruit_item.xml,代码如下
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent” >
<ImageView
android:id=“@+id/fruite_image”
android:layout_width=“wrap_content”
android:laytout_height=“wrap_content” />
<TextView
android:id=“@+id/fruit_name”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_gravity=“center”
android:layout_marginLeft=“10dp” />
</LinearLayout>
上面的代码中,我们定义了一个ImageView用于显示水果的图片,又定义了一个TextView用于显示水果的名称,并让TextView在垂直方向上居中显示。
接下来我们创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Fruit类,新建类FruitAdapter:
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId; // 图片资源id
public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects) {
super(context, textViewResourceId, objects);
resourceId = textViewResoureceId;
}
@Override
public View getView(int positon, VIew convertView, ViewGroup parent) {
Fruit fruit = getItem(position); // 获取当前项的Fruit实例
View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, flase);
ImageView fruitImage = (ImageView) findViewById(R.id.fruit_image);
TextView fruitName = (TextView) findViewById(R.id.fruit_name);
fruitImage.setImageResource(fruit.getImageId());
fruitName.setText(fruit.getName());
return View;
}
}
FruitAdapter重写了父类的一组构造函数,用于将上下文,ListView子项布局的id和数据传递过去。另外又重写了getVIew()方法,这个方法在每一个子项被滚动到屏幕内的时候会被调动。在getView()方法中,首先通过getItem()方法得到当前项的Fruit实例,然后使用LayoutInflater来为这个子项加载我们传入的布局。
这里的Layouinflater的inflater()方法接收3个参数,前面两个参数我们都知道是什么意思了,第三个参数指定成flase,表示只让我们在父布局中声明的layout属性生效,但不为这个View添加父布局,因为一旦View有了父布局之后,它就不能再天机到ListView中了。(现在不太明白没关系,这其实是ListView中的标准写法)
最后我们修改MainActivity中的代码,
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);
}
private void initFruit() {
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.apple_pic);
fruitList.add(banana);
…
}
}
}
只要修改fruit_item.xml中的内容,就可以定制出各种复杂的界面了。
3.5.3 提升ListView的运行效率
目前我们的LIstView的运行效率是很低的,因为在FruitAdapter的getView()方法中,每次都将布局重新加载了一遍,当ListView快速滚动的时候,这就会成为性能的瓶颈。
仔细观察,getView()方法还有一个converView的参数,这个参数用于将之前加载好的布局进行缓存,以便之后可以进行重用。修改FruitAdapter中的代码,如下
public class FruitAdapter extends ArrayAdapter<Fruit> {
…
@Override
public View getView(int positon, View convertView, VIewGroup parent) {
Fruit fruit = getItem(position);
View view;
if (convertView == null) {
view = LayoutFlater.from(getContext()).inflate(resourceId, parent, false);
} else {
view = convertView;
}
ImageView fruitImage = (ImageView)view.findViewById(R.id.fruit_image);
TextView fruitName = (TextView)view.findViewById(R.id.fruit_name);
fruitImage.setImageResource(fruit.getImageId());
fruitName.setText(fruit.getName());
return view;
}
}
虽然现在已经不会重复加载布局,但是每次在getView()的时候,还是会调用View的findViewById()方法来获取一次控件的实例。继续优化,使用ViewHolder来使得不会重复创建。
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position);
View view;
ViewHolder viewHolder;
if (convertVIew == null) {
view = LayoutFlater.from(getContext()).inflate(resourceId, parent, false);
viewHolder = new ViewHolder();
veiwHolder.fruitImage = (ImageView)view.findViewById(R.id.fruit_image);
viewHolder.fruitName = (TextView)view.findViewById(R.id.fruit_name);
view.setTag(viewHolder); // 将ViewHolder存储在View中。
} else {
view = convertView;
viewHolder = (ViewHolder)view.getTag();
}
viewHolder.fruitImage.setImageResource(fruit.getImageId());
viewHolder.fruitNma.setText(fruit.getName());
return view;
}
class ViewHolder {
ImageView fruitImage;
TextView fruitName;
}
3.5.4 ListView的点击事件
修改MainActivity中的代码
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Fruit fruit = fruitList.get(position);
Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show();
}
});
3.6 更强大的滚动控件 — RecyclerView
ListView虽然很好用,扩展性不好,只能实现纵向的滚动效果,而且如果我们不使用一些技巧来提升它的运行效率,那么LIstView的性能就会非常差。
RecyclerView是增强版的ListView,可以轻松实现ListView的效果,还优化了ListView的各种不足之处。目前Android更推荐使用RecyclerView。
新建一个RecyclerViewTest的项目
android团队将RecyclerView定义在support库当中,想要使用RecyclerVIew这个控件,需要再项目中添加相应的依赖库才行。
打开app/build.gradle文件,在dependencies中添加如下内容
dependencies {
compile fileTree(dir: ‘libs’, include: [‘*.jar’])
compile ‘com.android.support:appcompat-v7:24.2.1’
compile ‘com.android.support:recyclerview-v7:24.2.1’
testCompile ‘junit:junit:4.12’
}
Warming:
新版本配置如下:recyclerview的版本应该和appcompat的版本保持一致。
dependencies {
implementation fileTree(dir: 'libs', include: ['.jar'*])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.recyclerview:recyclerview:1.1.0-beta02'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
添加完之后点击Sync Now来进行同步一下,然后修改activity_main.xml中的代码,
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent” >
// 由于RecyclerView并不是在系统SDK中,所以需要把完整的包名路径写出来
<android.support.v7.widget.RecyclerView
android:id=“@+id/recycler_view”
android:layout_width=“match_parent”
android:layout_height=“match_parent” />
</LinearLayout>
为了使得RecyclerView达到和ListView相同的效果。我们需要把图片。Fruit类,fruit_item.xml复制过来。免得再写一遍。
新建一个新的FruitAdapter,继承自RecyclerVIew.Adapter,并将泛型指定为FruitAdapter.ViewHolder
。。。
3.6.2 RecyclerView实现横向滚动和瀑布流布局
首先对fruit_item.xml进行修改,因为这个布局目前是水平排列的。
<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:layotu_height=“wrap_content”
android:layout_gravity=“center_hotizontal”
android:layout_marginTop=“10dp” />
</LinearLayout>
现在修改MainActivity的代码
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_main);
initFruit();
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientaion(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(layoutManager);
FruitAdapter adapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
}
}
调用LinearLayoutManger的setOrientation()方法来设置布局的排列方向,默认是纵向排列的,我们传入LinearLayoutManger.HORIZONTAL表示让布局横行排列。
ListView的布局排列是由自身去管理的,而RecyclerView则将这个工作交给了LayoutManger,LayoutManage中制定了一套壳拓展的布局排列接口,子类只要按照接口的规范来实现,就能制定出各种不同排列方式的布局了。
处理LinearLayoutManger,RecyclerView还给我们提供了GridLayoutManger和StaggeredGridLayoutManager这两种内置的布局排列方式。GridLayoutManaget可以用于实现网格布局,StaggeredGridLayoutManager可以用于实现瀑布流布局。
现在我们来实现更加炫酷的瀑布流布局(相当于iOS中的UICollectionVeiw)
继续修改fruit_item.xml
<LinearLayout xmlns:android=“http://schemas.adnroid.com/apk/res/android”
adnroid: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_contetn”
android:layout_height=“wrap_contetn”
android:layout_gravity=“Left”
android:layout_marginTop=“10dp” />
</LinearLayout>
这里做了一些小调整,首先将LinearLayout的宽度由100dp修改为match_parent,因为瀑布流布局的宽度应该是根据布局的列数来自动适配的。而不是一个固定的值。另外我们使用了layout_maring属性来让子项之间互留一点间距。还有将TextView的对齐属性改成了居左对齐。
接着修改MainAcitivity代码:
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundel savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main):
initFruits();
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
// 构造函数接收两个参数,3表示布局分为3列,第二个参数用于指定布局的排列方式。
StaggerdGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTCAL);
recyclerView.setLayoutManager(layoutManager);
FruitAdapter adapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
}
private void initFruit() {
for (int i = 0; i < 2; i++) {
Fruit apple = new Fruit(getRandomLengthName(“Apple”, R.drawable.apple_pic);
fruitList.add(apple);
…
}
}
private Strring 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.6.3 RecyclerView的点击事件
不同于ListView,RecyclerView并没有提供类似于setOnItemClickListener()这样的注册监听器方法。而是需要我们自己给子项具体的view去注册点击事件,相对于ListView来说,实现起来要复杂一点。
ListView的点击事件上的处理并不人性化,setOnItemClickListener()方法注册的是子项的点击事件,但如果我们想点击的是子项里面具体的某一个按钮呢,ListView也能实现,但是实现起来就相对比较麻烦了。为此,RecyclerView干脆直接摒弃了子项点击事件的监听器,所有的点击事件都由具体的View去注册,就再没有这个困扰了。
修改FruitAdapter中的代码:
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
private List<Fruit> mFruitList;
static class ViewHolder extends RecyclerView.ViewHolder {
View fruitView;
ImageView fruitImage;
TextView fruitName;
public ViewHolder(View view) {
super(view);
fruitView = view;
fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
fruitName = (TextView) view.findViewById(R.id.fruit_name);
}
}
public FruitAdapter(List<Fruit> fruitList) {
mFruitList = fruitList;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
final ViewHolder holder = new ViewHolder(view);
holder.fruitVeiw.setOnClickListener( new View.OnClickListener() {
@Ovrride
public void onClick(View v) {
int position = holder.getAdapterPosition();
Fruit fruit = mFruitList.get(position);
Toast.makeText(v.getContext(), “you clicked view” + fruit.getName(), Toast.LENTH_SHORT()).show();
}
)};
holder.fruitImage.setOnclickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = holder.getAdapterPositon();
Fruit fruit = mFruitList.get(position);
Toast.makeText(v.getContext(), “you clicked view” + fruit.getName(), Toast.LENTH_SHORT).show();
}
)};
return holder;
}
…
}
RecyclerView的强大之处在于,它可以轻松实现子项中任意控件或布局的点击事件。
3.7 编写界面的最佳实践。
现在我们结合之前所有的知识,来做衣蛾较为复杂且没管的聊天界面。创建一个UIBestPractice项目。
具体代码看Android Studio里面的代码