自定义控件和ListView,RecyclerView
引入布局
Android:background可以为布局或者控件指定一个背景;
Android:layout_margin可以指定控件在上下左右方向上偏移的距离,也可以使用android:layout_marginLeft等属性来单独指定控件在某个方向上偏移的距离。
成功定义一个布局之后,在主布局文件里面用将其引入;
隐藏系统自带标题栏的方式:调用gerSupportActionBar()来获得ActionBar实例,然后调用ActionBar的hide()将标题栏隐藏起来。
创建自定义控件
1.新建类继承LinearLayout,成为自定义控件;
2.重写LinearLayout里面带有两个参数的构造函数,在布局里面引入自定义控件就会调用这个构造函数,通过LayoutInflater的from()方法可以构建出一个LayoutInflater对象,然后调用inflate()方法可以动态加载一个布局文件,inflater()方法接收两个参数,(第一个参数是要加载布局文件的id,第二个参数是给加载好的布局再添加一个父布局);
3.在布局文件里面添加自定义控件;
ListView
ListView的简单用法
1.在.xml里面加入ListView布局;
2.在类里面定义数据。然后将数据传递给ListView,这里借助适配器完成:
将ArrayAdapter泛型指定成String;
在ArrayAdapter的构造函数中依次传入当前上下文,ListView子项布局的id,要适配的数据;
调用ListView的setAdapter()方法,将构建好的适配器对象传递进去;
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的界面
1.定义一个实体类,作为ListView适配器的适配类型;
2.为ListView的子项指定一个自定义布局;
3.创建一个自定义的适配器,继承ArrayAdapter,泛型指定为刚刚自定义的实体类;在新建的适配器里重写了父类的一组构造函数,将上下文,ListView子项布局的id,数据都传递进去;重写getView()方法,首先通过getItem()得到当前的实例,然后通过LayoutInflater来为这个子项加载我们传入的布局;
LayoutInflater的inflate()接收三个参数,第三个参数是false是表示只让我们在父布局中声明layout属性生效,不会为这个View添加父布局,因为一旦View有了父布局之后,他就不能再添加到ListView中了。
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId;
public FruitAdapter(Context context, int textViewResourceId,
List<Fruit> objects) {
super(context, textViewResourceId, objects);
resourceId = textViewResourceId;
}@Override
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position); // 获取当前项的Fruit实例
View view = LayoutInflater.from(getContext()).inflate(resourceId, parent,
false);
ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
fruitImage.setImageResource(fruit.getImageId());
fruitName.setText(fruit.getName());
return view;
}
}
提升ListView的运行效率
在getView方法里面,每次都将布局重新加载了一遍,当ListView快速滚动的时候,这就会成为性能的瓶颈。
在getView()方法里面还有一个convertView参数,这个参数是用于将之前加载好的布局进行缓存,以便之后可以进行重用。
我们可以在getView()方法里面进行判断,如果convertView为null,那么就使用LayoutInflater去加载布局,如果不是null则直接对convertView进行复用;
public class FruitAdapter extends ArrayAdapter<Fruit> {
...
@Override
public View getView(int position, View convertView, ViewGroup parent) {
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;
}
}
在此优化基础上,虽然现在不会再重复去加载布局了,但是在每次getView()方法里面还是会去调用View的findViewById来获取一次控件的实例,这时我们可以借助ViewHolder来对这部分性能进行优化;
新增一个内部类ViewHolder,用于对控件的实例进行缓存,当convertView为null的时候,创建一个ViewHolder对象,将控件的实例都存在ViewHolder里面,然后调用View的setTag()方法,将ViewHolder对象存储在View中,当convertView不为null的时候,则调用View的getTag()方法,将ViewHolder重新取出来(这样控件存在ViewHolder里面就不用每次通过findViewById去获取控件实例了)。
public class FruitAdapter extends ArrayAdapter<Fruit> {
...
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position);
View view;
ViewHolder viewHolder;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, parent,
false);
viewHolder = new ViewHolder();
viewHolder.fruitImage = (ImageView) view.findViewById
(R.id.fruit_image);
viewHolder.fruitName = (TextView) view.findViewById (R.id.fruit_name);
view.setTag(viewHolder); // 将ViewHolder存储在View中
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag(); // 重新获取ViewHolder
}
viewHolder.fruitImage.setImageResource(fruit.getImageId());
viewHolder.fruitName.setText(fruit.getName());
return view;
}
class ViewHolder {
ImageView fruitImage;
TextView fruitName;
}}
ListView的点击事件
使用setOnItemClickListener()为ListView注册一个监听器,当用户点击了ListView里面任意一个子项,就回调onItemClick()方法,然后通过position参数判断用户点击的是哪一个子项,然后获取对应的实例,用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();
}
});
}
...
}
RecyclerView
RecyclerView的基本用法
1.在build.gradle里添加相应的依赖库;
2.在布局文件里加入RecyclerView,因为它不是内置在系统SDK里面的,所以需要把完整的包路径写出来;
3.为RecyclerView准备一个适配器,新建一个类,让这个适配器继承RecyclerView.Adapter,将其泛型指定为类名.ViewHolder,ViewHolder时我们在类里定义的一个内部类,这个内部类继承RecyclerView.ViewHolder,ViewHolder的构造函数里面传入一个View参数(这个参数通常是RecyclerView子项的最外层布局);
4.由于这个类时继承RecyclerView.Adapter的,那么就要重写onCreateViewHolder(),onBindViewHolder(),getItemCount()这三个方法。
onCreateViewHolder()是用于创建ViewHolder实例的,在这里面可以将自定义布局传进去,然后创建一个ViewHolder实例,把加载出来的布局传入到构造函数里面,最后将ViewHolder实例返回;
onBindViewHolder()是用于对RecyclerView子项的数据进行赋值的,会在每个子项被滚动到屏幕内的时候执行,通过position参数得到当前项的实例,然后再将数据设置到ViewHolder的对应数据里面;
getItemCount()用于告诉RecyclerView一共有多少个子项,直接返回数据源的长度就可以了;
5.在MainActivity函数里面,使用了一个initFruits()方法,用于初始化所有数据,然后再OnCreate()方法里面先获取到了RecyclerView的实例,然后创建了一个LinearLayoutManager对象,并且将它设置到RecyclerView中。(LayoutManager用于指定RecyclerView的布局方式,这里LinearLayoutManager是线性布局的意思,可以实现和ListView类似的效果),然后创建了适配器的实例,将数据传入到适配器的构造函数里面,最后调用RecyclerView的setAdapter()方法来完成适配器的设置,这样RecyclerViewg和数据之间的关联就建立完成了。
实现横向滚动和瀑布流布局
把前面自定义的元素布局改成垂直排列;在MainActivity里面加入一行代码:调用LinearLayout的setOrientation()方法来设置布局的排列方向(默认是纵向排列的),传入LinearLayout.HORIZONTAL表示让布局横向排列;
ListView的布局排列是由自身去管理的,RecyclerView将这个工作交给了LayoutManager,LayoutManager里面定制了一套可扩展的布局排列接口。除了LinearLayoutManager还有GridLayoutManager(网格布局)和StaggeredGridLayoutManager(瀑布流布局)
在onCreate()方法里面,创建一个StaaggerdeGiridLayoutManager的实例,StaaggeredGrildLayoutManager的构造函数接收两个参数,第一个参数用于指定布局的列数,第二个参数用于指定布局的排列方向,传入StaggeredGridLayoutManager.VERTICAL表示让布局纵向排列;然后再把创建好的实例设置到RecyclerView中就好了。(因为瀑布流的布局需要各个子项的高度1不一致才可以看到效果,所以可以使用gerRandomLengthName()这个方法,使用random对象来创造一个1到20之间的随机数)
RecyclerView的点击事件
RecyclerView没有提供类似于setOnItemClickListener()这样的组测监听的方法,所以需要自己给子项具体的View注册点击事件;
先修改ViewHolder,在ViewHolder里面添加变量来保存子项最外层布局的实例,然后再onCreateViewHolder()方法里面注册事件就好了