There’s a phrase in Buddhism, ‘Beginner’s mind.’ It’s wonderful to have a beginner’s mind.
佛教中有一句话:初学者的心态;拥有初学者的心态是件了不起的事情。——乔布斯
小弟初学安卓,该文算是小弟的学习过程,课后笔记与一些自己的思考,希望在自己的自学路上留下印记,也许有一些自己想的不对的地方,希望各位前辈斧正。
一.ListView与Adapter
1.说说Adapter
Adapter,中文翻译为适配器,我们生活中肯定能见到一种适配器,就是电源适配器。例如笔记本的电源适配器,我国家庭标准电压为220V,而实际上笔记本电脑所需要的电压并不是220V,为了让笔记本正常工作,就需要将220V交流电通过变压、整流、滤波、稳压,输出为适合笔记本的电压。
而在安卓中,为了使数据能以合适的方式在视图上显示,就得用到适配器。数据有许多种类型,就像是各种不同的”输入电压“,而经过适配器的处理,就能稳定输出视图能接受的”电压“。我们常用的适配器一共有:ArrayAdapter,SimpleAdapter等,他们都是继承于BaseAdapter 。而小弟这次主要初步了解了BaseAdapter,以后再详细研究ArrayAdapter,SimpleAdapter。
2.初识ListView
List为”列;列表“的意思,实际上ListView就是我们常见的列表视图,例如电话簿,QQ好友列表这些都是ListView展示的。许多图形界面编程里都有ListView这个控件。而在安卓中,ListView具有以下几个重要的属性:
android:listSelector:设置列表项被点击时的背景色。
android:scrollingCache:如果设为true,在滚动时会使用绘制缓存。
android:divider:设置列表项的分割线(既可以是颜色分隔也可以是Drawable分隔)。
android:dividerHeight:设置列表项的分割线的高度。
android:headerDividerEnabled:如果设为false,则不在header View之后绘制分割线。
android:footerDividerEnabled:如果设为false,则不在footer View之前绘制分割线。
3.ListView与Adapter的关系
说了这么多,为什么在讲ListView之前要提Adapter呢?因为要在ListView列表项显示数据,做出我们想看到的列表样式,就必须用到Adapter。引用一下别人的说法,就是把新建的ListView当成一个”柜子“的话,Adapter的任务就是将要展示的复杂数据,处理为一个个规格符合要求的”抽屉“,一层一层的塞入”柜子“,使这个”柜子“变得完整可用。这一层层”抽屉“就是列表视图的ITEM列表项。所以说,ListView与Adapter是密不可分的!它们是真心相爱的!!! 顺带一提ListView的父类的父类是AdapterView,未来小弟会做更深的了解!
二、制作一个简单的ListView
以我自己模仿微信的关于界面做的一个样式为例吧(标题返回按钮没做),
这个页面只用了一个布局和一个ListView控件,小弟将慢慢讲述我的学习过程。
1.布局的设计
我想到了好几个方法,接下来小弟会一一的讲述:
⑴两个线性布局夹一个ListView?
这个是我最初的想法,并且我还实践了,制作出来的效果很接近了,如图:
可是当我自己用手机进到了微信的关于微信界面看了之后,发现自己很蠢,有以下几个理由:
①最外面一层线性布局,里面还有两个线性布局?太浪费资源了吧!
②请看上图的最下边的字,那是我用来测试的,它的下面还有四个字“短短短短”未显示,上图的模拟器分辨率为768×1280,当我换为1440×2560的模拟器时,“短短短短”就显示了,显然分辨率为768×1280的手机要显示下面的文字,就得往上滑动一下,然而,布局本身是没有滑动功能的。说了这么多其实就是我发现微信的那个界面当内容没有完全展示时,它是可以滑动的,所以我的第一个设计肯定是错误的。
⑵最外面裹一层ScrollView试试?
这个我稍微尝试了一下,发现很不好用,首先因为这个界面中间这块ListView因为ITEM较少,所以我们设置ListView的属性android:layout_height=”wrap_content”来使它展示所有选项,而不是令它有选项未展示而需要去滑动。而当在外面裹一层ScrollView时,这个属性就会使ListView只显示一行选项,必须去给它设置高度,而且ListView还不能滑动了,小弟度娘了一下ScrollView嵌套了ListView,发现其中有些复杂的原因,上面出现的问题也是可以解决的,小弟就不在这多说了,因为我们要制作的界面实际上比较简单,不需要做的那么复杂而且嵌套层次太多很浪费。
⑶就用一个ListView吧!
既然需要用到滑动,干脆就直接使用ListView的滑动功能不就好了么?不过这样的话这个界面列表上下的灰色区域怎么做呢?小弟在上网络课程时听老师无意提起过ListView的addHeaderView()与addFooterView()方法,一开始我不知道HeaderView与FooterView是啥玩意,在线翻译了一下似乎是页眉与页脚的意思。总之让我们先开始吧。
首先是页面主布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/gray"
tools:context="com.qdf.wechatabout.MainActivity">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
/>
</RelativeLayout>
然后是MainAcitivity.java代码:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private ListView mListView;
private LayoutInflater mLayoutInflater;//声明布局解析器
private List<String> mList; //作为数据源
private WeChatAboutAdapter mAdapter;//声明适配器
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mList = new ArrayList<>();//存放列表字符串的集合
mList.add("去评分");
mList.add("功能介绍");
mList.add("系统通知");
mList.add("帮助与反馈");
mList.add("检查新版本");
mAdapter = new WeChatAboutAdapter(MainActivity.this, mList);//实例化适配器
/**令布局解析器获得系统布局解析服务*/
mLayoutInflater = (LayoutInflater) MainActivity.this.getSystemService(LAYOUT_INFLATER_SERVICE);
View headerView = mLayoutInflater.inflate(R.layout.headview_layout, null);
View footerView = mLayoutInflater.inflate(R.layout.footerview_layout, null);
mListView = (ListView) findViewById(R.id.listView);
mListView.addHeaderView(headerView, null, false);//添加HeaderView,第三个参数为false表示不可被选择
mListView.addFooterView(footerView, null, false);//添加FooterView,第三个参数为false表示不可被选择
mListView.setAdapter(mAdapter);//设置适配器
}
}
来说说上面的代码,全局变量已经注释了。首先添加字符串到集合,作为待会分到每一选项的数据,然后解析自定义的headerview_layout.xml和footerview_layout.xml变为视图headerView与footerView并添加进mListView。这样列表视图的首尾项就能实现我们需要的灰色区域效果,之后给mListView设置适配器来将之前添加进mList的数据展示到每一个ITEM,要注意的是addHeaderView与addFooterView必须写在setAdapter之前,原因是当我们在调用setAdapter方法时会 android会判断当前listview是否已经添加header或者footer。在设置OnItemClickListener时,HeaderView的position值为0,依次往下排。值得一提的是,在我们自定义的adapter中,position值是不包含HeaderView和FooterView的,所以我们在适配器中为每项展示数据不会影响到HeaderView和FooterView。(P.s:positon就是一个选项在ListView的位置)
然后是自定义适配器类WeChatAboutAdapter.java的代码:
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public class WeChatAboutAdapter extends BaseAdapter {
private Context mContext;
private List<String> mList = new ArrayList<>();
private LayoutInflater mLayoutInflater;
/**
* 构造方法
* @param context 上下文
* @param list 需要展示的数据集合
*/
public WeChatAboutAdapter(Context context, List<String> list) {
mContext = context;
mList = list;
mLayoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getCount() {
return mList.size();
}
@Override
public Object getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
//convertView只有为null时才会进行一次引用,之后重用,控件相同
if (convertView == null) {
convertView = mLayoutInflater.inflate(R.layout.wexin_item_layout, null);
viewHolder = new ViewHolder();
viewHolder.textView = (TextView) convertView.findViewById(R.id.textView);
convertView.setTag(viewHolder);//将viewHolder作为convertView的标签(额外的数据)
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.textView.setText(mList.get(position));
return convertView;
}
/**
* 该内部类ViewHolder用来进行控件的重用
*/
class ViewHolder {
TextView textView;
}
}
首先这个类的getItem方法得到的position是不包含HeaderView和FooterView的。在第一次调用getView方法时,convertView此时为null,我们为其引用解析好的ITEM_Layout视图,此时item1的视图就完成了,而因为我们要求“柜子”的“抽屉”样式相同,只是里面摆放的东西(数据)不一样,为了节省资源,convertView应该复用,我们让它只有在为空值时才进行引用,这样下一次使用无需再创建新的视图,只需要改变返回的数据即可,之后成为了item2。视图内控件也应当重用,利用ViewHolder,convertView为空时实例化ViewHolder然后获取控件,并将ViewHolder作为convertView的标签存放控件,复用时调用getTag()。
最后附上为HeaderView和FooterView创建的布局文件:
headerview_layout.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="@color/gray"
android:gravity="center_horizontal"
android:orientation="vertical">
<ImageView
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_marginTop="20dp"
android:src="@drawable/weixin"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="20dp"
android:text="@string/version_string"
android:textSize="20sp"/>
</LinearLayout>
footerview_layout.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="@color/gray"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:gravity="center"
android:text="使用条款和隐私政策"
android:textColor="@color/light_blue"
android:textSize="12sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="25dp"
android:gravity="center_horizontal"
android:text="腾讯公司 版权所有\nCopyright© 2011-2015 Tencent\nAll Rights Reserved"
android:textColor="#A8A8A8"
android:textSize="10sp"/>
</LinearLayout>
三、一些功能的实现
1.监听ITEM点击事件
因为ListView由多个选项组成一个整体,所以我们应该要监听每个ITEM的点击,这就需要使用setOnItemClickListener()而不是setOnClickListener(),且如果使用setOnClickListener()时会报错。
以下为setOnItemClickListener()代码:
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
if(position==1){
mList.clear();
mList.add("测试");
mList.add("测试");
mList.add("测试");
mAdapter.notifyDataSetChanged();
}
Toast.makeText(MainActivity.this,"第"+position+"项被点击了",Toast.LENGTH_SHORT).show();
}
});
(P.s:在适配器类中重写isEnabled(int position)可以控制ITEM能否被选择)
而点击事件的内容就是我们要实现的另一个功能——数据更新。
2.简单的数据更新
可见上面监听器代码中设置了,当position值为1时,清空mList集合并添加了新的数据,但此时还需要一个步骤才能再视图中更新数据,那就是让适配器调用notifyDataSetChanged()。要注意的是,position是从0开始的,如果有HeaderView则其position为0。另外HeaderView和FooterView能使用setOnClickListener(),因为它们是单独的视图。(可能说的不太准确)
还有一些功能小弟还没研究透,真是感慨编程的博大精深啊,哈哈哈哈(无奈的苦笑)。
参考资料:
- http://www.jianshu.com/p/7a66063b7889
- http://www.jianshu.com/p/49cb5c30788f
网络零散的资料
2016年7月24日
星期日