Android app开发学习笔记——Android高级控件-上
一、ListView(列表视图)
在Android开发中,ListView是很常用的控件,以列表的形式展示具体的内容,并且能够根据数据的长度自适应显示
列表的显示需要三个元素:
ListView:用来展示列表的View
适配器:用来把数据映射到ListView上的中介
数据源:ListView中的每一行View对应数据源的一条数据
1.建立适配器(以BaseAdapter为例)
其余类型可以参考:适配ListView的几种常见Adapter的用法总结
BaseAdapter是最万能最好用的数据适配器之一,可以给ListView、Spinner、GridView多种控件填充数据。通俗的说,适配器的作用就是在数据和视图之间建立一种桥梁,类似一个转换器,能够将复杂的数据转换成用户可以接受的方式进行呈现。
我们新建一个java文件,继承自BaseAdapter,并且实现它的4个基础方法(这4个基础方法可能不会自动生成需要我们自己输入)。
public class ListViewAdapter1 extends BaseAdapter{
@Override
public int getCount() {
return 0;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return null;
}
}
getCount : 要绑定的条目的数目,比如格子的数量
getItem : 根据一个索引(位置)获得该位置的对象
getItemId : 获取条目的id
getView : 获取该条目要显示的界面
可以简单的理解为,adapter先从getCount里确定数量,然后循环执行getView方法将条目一个一个绘制出来,所以必须重写的是getCount和getView方法。而getItem和getItemId是调用某些函数才会触发的方法,如果不需要使用可以暂时不修改。
下面我们具体介绍BaseAdapte的四个基础方法的使用
1->getCount
返回值为填充的item个数,即列表有多少列
比如我们使用
public int getCount() {return data.size();}
就可以得到匹配data数据源的item行数
2->getView
其作用为填充每个item的可视内容并返回
其中getView方法中的三个参数,position是指现在是第几个条目;convertView是旧视图,就是绘制好了的视图;parent是父级视图,也就是ListView之类的。
我们用inflate方法绘制好后view,最后return返回给getView方法就可以了。
补充:
1.getView方法是由系统自动回调的方法,每当可视区域内需要刷新一个item时就会被调用,来填充数据并绘制View。
2.界面启动时,自动调用getView方法传入的convertView均为null; 根据代码逻辑
if(convertView == null){
convertView = View.inflate(MainActivity.this,R.layout.item_view,null);
findViews(ics,convertView); convertView.setTag(ics); }
当convertView为null时就是调用View.inflate给其赋值,当然最后只要返回convertView就行了
3->getItem&getItemId
它也不会被自动调用,它是用来在我们设置setOnItemClickListener、setOnItemLongClickListener、setOnItemSelectedListener的点击选择处理事件中方便地调用,来获取当前行数据的。
至于getItemId(int position),它返回的是该postion对应item的id
不同getItem的是,某些方法(如onclicklistener的onclick方法)有id这个参数,而这个id参数就是取决于getItemId()这个返回值的。
详细可以参考:android中Baseadapter的 getItem 和 getItemId 的作用和重写
这边再给出一个适配器的样例,用于下一节的简单使用
public class ListViewAdapter1 extends BaseAdapter{
private List<String> data;
private LayoutInflater inflater;
public ListViewAdapter1(Context context,List<String> data){
//加载布局管理器
inflater=LayoutInflater.from(context);
this.data=data;
}
//数据源的长度
@Override
public int getCount() {
return data.size();
}
//每一行绑定的数据源
@Override
public Object getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
//获取每一行的View
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if(convertView==null)
{
viewHolder=new ViewHolder();
//xml文件加载成View
//android.R.layout.simple_list_item_1是系统定义好的布局文件只显示一行文字,数据源
convertView=inflater.inflate(android.R.layout.simple_list_item_1,parent,false);
//利用view对象,找到布局中的组件
viewHolder.text=(TextView) convertView.findViewById(android.R.id.text1);
convertView.setTag(viewHolder);
}
else {
//使用tag,不用每次加载xml,提高速度
viewHolder=(ViewHolder) convertView.getTag();
}
viewHolder.text.setText(data.get(position));
return convertView;
}
private class ViewHolder{private TextView text;}
}
代码解读参考BaseAdapter使用教程及方法详解中的ViewHolder优化
2.ListView简单使用
布局文件中仅需添加如下代码
<ListView
android:id="@+id/liebiao"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
在活动文件中我们用setAdapter选择应用我们自己创建的适配器
使用List类型的变量作为数据源,存储数据源的对应每一条数据
public class liebiao extends AppCompatActivity {
private ListView listView;
private BaseAdapter adapter;
private List<String> items;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_liebiao);
initData();
listView=(ListView)findViewById(R.id.liebiao);
listView.setAdapter(adapter = new ListViewAdapter1(this,items));
}
//初始化数据
private void initData(){
items =new ArrayList<>();
for(int i=0;i<20;i++)
{items.add("item:"+(i+1));}
}
}
最终效果如下图
3.给每一行点击监听
不需要给ListView每一行的View设置点击事件,ListView源码已经封装了setOnClickListener方法
下面代码设置了ListView行点击事件,点击之后显示一个Toast
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(liebiao.this,"点击Item位置:"+(position+1),Toast.LENGTH_SHORT).show();
}
});
setOnItemClickListener参数参考:【转载】ListView中的setOnItemClickListener参数
4.设置分割线
只需要在布局中增加两个属性就行了
android:divider="@color/black"//分割线颜色
android:dividerHeight="5dp"//分割线高度
如果不想要分割线,可以设置分割线为空,或者将分割线颜色设为透明:
android:divider="@null"
或者
android:divider="@color/transparent"
5.添加header和footer
通过调用inflate方法将布局文件转化成View
head.xml,foot.xml文件在这里不显示了
View header= LayoutInflater.from(this).inflate(R.layout.head,null);
listView.addHeaderView(header);
View footer= LayoutInflater.from(this).inflate(R.layout.foot,null);
listView.addFooterView(foot);
6.动态修改Item
动态改变ListView只需要修改数据源,然后调用adapter的notifyDataSetChanged方法更新适配器即可
比如如下给尾部布局的点击添加一个添加项目功能
footer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId())
{
case R.id.foot:
items.add("我是被添加的");
adapter.notifyDataSetChanged();
break;
}
}
});
7.设置显示位置
setSelection(),传入一个index整型数值,就可以让ListView定位到指定Item的位置。
setSelectionFromTop()的作用是设置ListView选中的位置,同时在Y轴设置一个偏移量(padding值)。
listView.setSelection(10);//显示第10行的数
listView.setSelectionFromTop(10,500);//显示第10行的数,同时y轴加500偏移量
8.实现聊天界面
由于要实现不同样式的列表,所以我们新建一个Message类用于记载消息内容区别是我们还是对方发出的消息
public class Message{
private String content;//消息内容
private boolean sended;//是否发送
public Message(){}
public Message(String content,boolean sended){
this.content=content;
this.sended=sended;
}
public String getContent(){
return content;
}
public void setContent(String content){
this.content=content;
}
public boolean isSended(){
return sended;
}
public void setSended(boolean sended){
this.sended=sended;
}
}
下面是活动文件的主题我们以List代替List
public class liebiao extends AppCompatActivity {
private ListView listView;
private BaseAdapter adapter;
private List<Message> messages;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_liebiao);
init();
listView=(ListView)findViewById(R.id.liebiao);
//listView.setAdapter(adapter=new com.example.hzhapplication.ListViewAdapter(this,data));
listView.setAdapter(adapter=new ListViewAdapter1(this,messages));
}
private void init(){
messages = new ArrayList<>();
messages.add(new Message("为什么程序员到哪里都背着电脑包",false));
messages.add(new Message("因为他们没有别的包包。",true));
messages.add(new Message("程序员最烦两件事,第一件事是别人要他给自己的代码写文档,第二件呢?",false));
messages.add(new Message("是别人的程序没有留下文档。",true));
messages.add(new Message("如何生成一个随机的字符串?",false));
messages.add(new Message("让新手退出VIM",true));
}
}
适配与先前类似,添加了getViewTypeCount()和getItemViewType(int position)和两个类别用于分别辨识我们需要的样式
public class ListViewAdapter1 extends BaseAdapter{
private LayoutInflater inflater;
private List<Message> messages;
private final int TYPE_SEND=0;
private final int TYPE_ACCEPT=1;
public ListViewAdapter1(Context context,List<Message> data){
inflater=LayoutInflater.from(context);
this.messages=data;
}
//数据源的长度
@Override
public int getCount() {
return messages.size();
}
//每一行绑定的数据源
@Override
public Object getItem(int position) {
return messages.get(position);
}
@Override
public int getViewTypeCount() {
return TYPE_ACCEPT+1;
}
@Override
public int getItemViewType(int position){
if (messages.get(position).isSended()) {
return TYPE_SEND;
}
return TYPE_ACCEPT;
}
@Override
public long getItemId(int position) {
return position;
}
//获取每一行的View
@Override
public View getView(int position, View convertView, ViewGroup parent) {
int type = getItemViewType(position);
Message message=messages.get(position);
ViewHolder viewHolder;
if(convertView==null)
{
viewHolder=new ViewHolder();
if(type==TYPE_SEND)
{ convertView=inflater.inflate(R.layout.item_message_chat_send,null);}
else
convertView=inflater.inflate(R.layout.item_message_chat_accept,null);
viewHolder.tvContent=(TextView)convertView.findViewById(R.id.tv_content);
convertView.setTag(viewHolder);
}
else {
viewHolder=(ViewHolder) convertView.getTag();
}
//括号里一定要时string类型的,我有一次就写了一个position每次运行到这里app都会崩溃,心态都崩了
viewHolder.tvContent.setText(message.getContent());
return convertView;
}
private class ViewHolder{
private TextView tvContent;
}
}
接受样式与发送样式相似,这里仅给出发送的样式
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="7dip"
android:paddingTop="7dip">
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="55dp"
android:layout_toLeftOf="@+id/iv_message_from_head_image"
android:gravity="center"
android:paddingRight="20dip"
android:textColor="@color/black"
android:background="@drawable/duihuakuang"
android:textSize="17sp"
></TextView>
<ImageView
android:id="@+id/iv_message_from_head_image"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentRight="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:src="@mipmap/ic_launcher"
></ImageView>
</RelativeLayout>
duihuakuang布局由如下代码中的shapes下编写出
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!--圆角-->
<corners android:radius="10dp"/>
<!--填充颜色-->
<solid android:color="@color/浅蓝"/>
<!--描边-->
<stroke
android:width="2dp"
android:color="@color/black"/>
<!--内边距-->
<padding android:top="10dp" android:bottom="10dp"
android:left="10dp" android:right="10dp"/>
</shape>
最终效果
二、GridView(网格视图)
布局文件
GridView是按照行列的方式显示内容,一般用于显示图表列表。
首先是修改主布局文件,其中
android:numColumns="7":一行显示7列
android:scrollbars="none":去掉滚动条
android:verticalSpacing="10dp":两行之间的距离
android:horizontalSpacing="10dp":两列之间的距离
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".wangge">
<GridView
android:id="@+id/wangge"
android:numColumns="7"
android:scrollbars="none"
android:layout_marginBottom="10dp"
android:verticalSpacing="10dp"
android:horizontalSpacing="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
然后是书写每个网格内部的代码
<?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">
<ImageView
android:id="@+id/wanggetu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:scaleType="centerCrop"
android:src="@mipmap/zhengjing"/>
</LinearLayout>
活动文件
最后是活动文件与适配器,整体样式与ListView类似就不详细解释了
这里只给出一个疑惑解答:为什么将图像放入整数数组,这样可以存储吗?
这是由于android的SDK会自动为每个资源资源(图像,xml文件等)生成唯一的整数值。
比如当我们调用 R.drawable.resource_name如R.drawable.image1时,它就会返回一个Integer ID。
这就是为什么在您的代码中需要把R.drawable.image放入Integer Array的原因。
参考:为什么要在Android中将图像放入整数数组?
package com.example.h
public class wangge extends AppCompatActivity {
private GridView data;
private List<Integer> images;
private BaseAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wangge);
init();
data = (GridView) findViewById(R.id.wangge);
data.setAdapter(adapter=new GridViewAdapter(this));
}
private void init(){
images=new ArrayList<>();
for(int i=0;i<100;i++)
{
if(i%2==1){images.add(R.mipmap.zhengjing);}
else{images.add(R.mipmap.ic_launcher);}
}
}
public class GridViewAdapter extends BaseAdapter{
private LayoutInflater inflater;
public GridViewAdapter(Context context){
inflater=LayoutInflater.from(context);
}
//数据源的长度
@Override
public int getCount() {
return images.size();
}
//每一行绑定的数据源
@Override
public Object getItem(int position) {
return images.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
//获取每一行的View
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if(convertView==null)
{
viewHolder=new ViewHolder();
convertView=inflater.inflate(R.layout.grid_item,parent,false);
viewHolder.imageView=(ImageView) convertView.findViewById(R.id.wanggetu);
convertView.setTag(viewHolder);
}
else {
viewHolder=(ViewHolder) convertView.getTag();
}
viewHolder.imageView.setImageResource(images.get(position));
return convertView;
}
private class ViewHolder{private ImageView imageView;}
}
}
最终效果如图所示
部分代码及内容参考自《Android app开发入门到精通》