Android UI ListView讲解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/CodingEnding/article/details/79512087

前言

在Android系统中,针对大量数据的展示,可以使用ListView以列表的形式的呈现。虽然现在ListView在很多地方都被RecyclerView取代了,但是在一些合适的场景中依旧有用武之地。本文将详细讲解ListView的使用和常用技巧。

基本使用

ListView的使用还是很简单的,重点在于数据由Adapter(适配器)提供的,ListView并不直接访问数据源。因此,可以将ListView的使用分为3步:

  1. 获得数据源(如数组,List等)
  2. 通过数据源建立适配器(如ArrayAdapter等)
  3. 为ListView设置适配器

使用系统提供的布局

针对一些简单的场景(如只需要展示字符串),使用系统提供的ArrayAdapter即可。ArrayAdapter使用数组或List作为数据源,常用的两个构造方法如下:

//resource:列表项的布局文件
//objects:数据源
public ArrayAdapter(Context context,@LayoutRes int resource,T[] objects);
public ArrayAdapter(Context context,@LayoutRes int resource,List<T> objects)

使用ListView的示例代码如下:

//初始化普通布局的ListView
String[] dataArray=new String[]{"coding","ending","CodingEnding","Github","coder","Android"};//1.建立数据源
ArrayAdapter<String> normalAdapter=new ArrayAdapter<String>(this,
        android.R.layout.simple_list_item_1,dataArray);//2.建立适配器
normalListView.setAdapter(normalAdapter);//3.设置适配器

ArrayAdapter中使用的android.R.layout.simple_list_item_1是系统提供的布局文件,其实就是一个TextView。

效果截图:

监听点击事件

//监听单击事件
normalListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Log.i(TAG,"当前位置:"+position);
        String msg= (String) parent.getAdapter().getItem(position);//获取选中对象
        Toast.makeText(ListViewActivity.this,msg,Toast.LENGTH_SHORT).show();
    }
});

//监听长按事件
normalListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
    @Override
    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
        Toast.makeText(ListViewActivity.this,"发生长按事件",Toast.LENGTH_SHORT).show();
        return true;
    }
});

可以看到,在监听器中通过parent.getAdapter().getItem(position)获取选中对象。注意,这个方法的返回值是Object对象,因此需要进行强制转换。

相关属性

android:divider:设置ListView各项之间的分割线 [color或drawable资源]
android:dividerHeight:分割线的高度
android:headerDividersEnabled:是否绘制每个HeaderView后的分割线 [默认为true]
android:footerDividersEnabled:是否绘制每个FooterView前的分割线 [默认为true]
android:listSelector:设置列表项被选中时的效果 [color或drawable资源]
android:fastScrollEnabled:是否在快速滑动的是否显示右侧的滑动块
android:scrollbars:设置滑动条的展示方式 [horizontal|vertical|none]
android:stackFromBottom:是否在初始状态时显示ListView的最底部。 [默认为false]

stackFromBottom这个属性需要简单解释一下:如果设置为true,那么打开ListView首先看到的就是最底部的内容,看起来就像是ListView已经滚动到了最后一行;如果设置为false,就和默认状态一样,首先看到第一行的内容。

自定义列表布局

如果需要展示的内容比较复杂(比如图片加文字),我们就应该自定义适配器,使用自己的布局去展示列表项。

基本步骤

首先,建立一个实体类Book:

扫描二维码关注公众号,回复: 3039162 查看本文章
public class Book {
    private String name;
    private int imageRes;//图片资源

    public Book(String name, int imageRes) {
        this.name = name;
        this.imageRes = imageRes;
    }
    @Override
    public String toString() {
        return name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getImageRes() {
        return imageRes;
    }
    public void setImageRes(int imageRes) {
        this.imageRes = imageRes;
    }
}

接着,自定义一个布局文件(左侧图片,右侧文字),本例中命名为listview_custom_item.xml,代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="4dp"
    android:layout_marginBottom="4dp"
    android:gravity="center_vertical">

    <ImageView
        android:id="@+id/book_image"
        android:layout_width="45dp"
        android:layout_height="45dp"
        android:layout_marginLeft="8dp" />
    <TextView
        android:id="@+id/book_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="8dp"
        android:textAllCaps="false"
        android:textSize="16sp" />
</LinearLayout>

然后,通过继承BaseAdapter实现我们自己的适配器,本例中命名为StyleListViewAdapter

public class StyleListViewAdapter extends BaseAdapter{
    private Context context;
    private List<Book> dataList;

    public StyleListViewAdapter(Context context, List<Book> dataList) {
        this.context = context;
        this.dataList = dataList;
    }

    @Override
    public int getCount() {
        return dataList.size();
    }
    @Override
    public Object getItem(int position) {
        return dataList.get(position);
    }
    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Book book=dataList.get(position);
        View view= LayoutInflater.from(context).inflate(R.layout.listview_custom_item,parent,false);
        ImageView bookImageView=view.findViewById(R.id.book_image);
        TextView bookNameView=view.findViewById(R.id.book_name);
        bookImageView.setImageResource(book.getImageRes());
        bookNameView.setText(book.getName());
        return view;
    }
}

可以看到,需要重写getCount、getItem、getItemId、getView这四个方法。此外,还要提供一个构造方法用于外界传入Context和数据源(本例中为List<Book>)。

使用ViewHolder提升运行效率

在实际使用中,通常会使用ViewHolder提升ListView的运行效率,这一方式将充分利用ListView中View的复用机制。

首先,在Adapter中建立一个静态内部类ViewHolder:

static class ViewHolder{
    ImageView bookImageView;
    TextView bookNameView;
}

然后,修改Adapter中的getView方法,复用已有的View:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    Book book=dataList.get(position);
    ViewHolder viewHolder;
    if(convertView==null){
        convertView= LayoutInflater.from(context).inflate(
                R.layout.listview_custom_item,parent,false);
        viewHolder=new ViewHolder();
        viewHolder.bookImageView=convertView.findViewById(R.id.book_image);
        viewHolder.bookNameView=convertView.findViewById(R.id.book_name);
        convertView.setTag(viewHolder);//存储ViewHolder
    }else{//复用已有的View
        viewHolder= (ViewHolder) convertView.getTag();
    }
    viewHolder.bookImageView.setImageResource(book.getImageRes());
    viewHolder.bookNameView.setText(book.getName());
    return convertView;
}

最后,在代码中为ListView设置自定义的适配器即可,代码如下:

//初始化自定义布局的ListView
List<Book> dataList=new ArrayList<>();
dataList.add(new Book("《小王子》",R.mipmap.ic_launcher));
dataList.add(new Book("《资本论》",R.mipmap.ic_launcher));
dataList.add(new Book("《三体》",R.mipmap.ic_launcher));
StyleListViewAdapter styleAdapter=new StyleListViewAdapter(this,dataList);
customListView.setAdapter(styleAdapter);

效果截图:

实现多布局列表

在实际使用中,列表项可能不止一种布局形式,典型的如通讯录列表就有联系人、标题(如A、B、C等)这两种形式的列表项。通过对Adapter的修改,可以通过ListView实现多布局列表。在这里,将介绍如何实现一个简单的多布局列表,最终的效果如下:

准备布局文件

在本例中,主要有两种列表项,即标题项和内容项。因此,准备两个对应的布局文件,分别命名为listview_multi_title.xmllistview_multi_item.xml,代码如下:

listview_multi_title.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/item_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="6dp" />
</LinearLayout>

listview_multi_item.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="4dp"
    android:layout_marginBottom="4dp"
    android:gravity="center_vertical">
    <ImageView
        android:id="@+id/item_image"
        android:layout_width="45dp"
        android:layout_height="45dp"
        android:layout_marginLeft="8dp" />

    <TextView
        android:id="@+id/item_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="8dp"
        android:textAllCaps="false"
        android:textSize="16sp"
        android:textColor="#000000"/>
</LinearLayout>

准备实体类

对于不同的布局而言,应该使用不同的实体类。在本例中,有两种列表项,因此需要两个实体类。首先可以建立一个基类,本例中命名为BaseMultiBean,代码如下:

public abstract class BaseMultiBean {
    public static final int TYPE_TITLE=0;//标题项
    public static final int TYPE_ITEM=1;//内容项
    protected int type;//类型

    public int getType() {
        return type;
    }
    public void setType(int type) {
        this.type = type;
    }
}

可以看到,基类中主要是封装了实体的类型属性,这一属性将用于确定要使用的列表项布局。然后,再建立两个继承自基类的实体类,分别对应标题项和内容项,本例中命名为TitleBeanItemBean,代码如下:

TitleBean

public class TitleBean extends BaseMultiBean{
    private String title;

    public TitleBean(String title) {
        this.title = title;
        this.type=TYPE_TITLE;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
}

ItemBean

public class ItemBean extends BaseMultiBean{
    private int imageRes;//图片资源
    private String content;//内容

    public ItemBean(int imageRes, String content) {
        this.imageRes = imageRes;
        this.content = content;
        this.type=TYPE_ITEM;
    }
    public int getImageRes() {
        return imageRes;
    }
    public void setImageRes(int imageRes) {
        this.imageRes = imageRes;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
}

创建适配器

有了布局和实体类,就可以开始着手创建适配器了,本例中命名为MultiListViewAdapter。和前面提到的适配器相比,还需要实现getViewTypeCountgetItemViewType这两个方法。此外,getView也需要修改,以及还要提供两种不同的ViewHolder分别对应两种列表项。示例代码如下:

public class MultiListViewAdapter extends BaseAdapter{
    ......
    @Override
    public int getViewTypeCount() {//返回类型种类数
        return 2;
    }
    @Override
    public int getItemViewType(int position) {//返回当前项的类型
        BaseMultiBean bean=dataList.get(position);
        return bean.getType();
    }

    static class TitleViewHolder{//针对标题项的复用
        TextView titleView;
    }
    static class ItemViewHolder{//针对内容项的复用
        ImageView itemImageView;
        TextView itemContentView;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        TitleViewHolder titleViewHolder;
        ItemViewHolder itemViewHolder;
        switch(getItemViewType(position)){//根据Item的类型不同,执行相应的操作
            case BaseMultiBean.TYPE_TITLE:
                TitleBean titleBean= (TitleBean) dataList.get(position);
                if(convertView==null){
                    convertView=inflater.inflate(R.layout.listview_multi_title,parent,false);
                    titleViewHolder=new TitleViewHolder();
                    titleViewHolder.titleView=convertView.findViewById(R.id.item_title);
                    convertView.setTag(titleViewHolder);
                }else{
                    titleViewHolder= (TitleViewHolder) convertView.getTag();
                }
                titleViewHolder.titleView.setText(titleBean.getTitle());
                break;
            case BaseMultiBean.TYPE_ITEM:
                ItemBean itemBean= (ItemBean) dataList.get(position);
                if(convertView==null){
                    convertView=inflater.inflate(R.layout.listview_multi_item,parent,false);
                    itemViewHolder=new ItemViewHolder();
                    itemViewHolder.itemImageView=convertView.findViewById(R.id.item_image);
                    itemViewHolder.itemContentView=convertView.findViewById(R.id.item_content);
                    convertView.setTag(itemBean);
                }else{
                    itemViewHolder= (ItemViewHolder) convertView.getTag();
                }
                itemViewHolder.itemImageView.setImageResource(itemBean.getImageRes());
                itemViewHolder.itemContentView.setText(itemBean.getContent());
                break;
            default:break;
        }
        return convertView;
    }
}

为ListView设置适配器

有了前面三步的准备工作,现在就可以着手为ListView设置适配器了,示例代码如下:

//初始化多状态布局的ListView(未设置点击监听)
List<BaseMultiBean> multiDataList=new ArrayList<>();
multiDataList.add(new TitleBean("第一个区域"));
multiDataList.add(new ItemBean(R.mipmap.ic_launcher,"《小王子》"));
multiDataList.add(new ItemBean(R.mipmap.ic_launcher,"《狮子王》"));
multiDataList.add(new TitleBean("第二个区域"));
multiDataList.add(new ItemBean(R.mipmap.ic_launcher,"《资本论》"));
multiDataList.add(new ItemBean(R.mipmap.ic_launcher,"《三体》"));
multiDataList.add(new ItemBean(R.mipmap.ic_launcher,"《孤独的进化者》"));
MultiListViewAdapter multiAdapter=new MultiListViewAdapter(this,multiDataList);
multiListView.setAdapter(multiAdapter);

常用技巧

设置空数据布局

public void setEmptyView(View emptyView);//在ListView的数据为空时显示emptyView

首先,在ListView所在的XML文件中定义一个EmptyView的布局,示例代码如下:

<FrameLayout
android:id="@+id/empty_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:textSize="20sp"
    android:text="暂无数据"/>
</FrameLayout>

提示: EmptyView的widthheight属性可以和ListView保持一致,这样在空数据时刚好可以让EmptyView占据ListView的空间。

然后,在代码中为ListView设置EmptyView,示例代码如下:

View view=findViewById(R.id.empty_view);
normalListView.setEmptyView(view);

效果截图:

隐藏滚动条

只需将android:scrollbars属性设置为none就可以隐藏ListView的滚动条。

去掉默认的选中颜色

只需将android:listSelector属性设置为#00000000就可以去掉默认的选中颜色(其实是设置为透明色)。

设置入场动画

android:layoutAnimation:为ListView设置布局动画。 [使用layoutAnimation资源]

为ListView设置layoutAnimation属性后,ListView的所有可见项都会执行指定的动画,有多少可见项就会执行多少次动画。主要的使用步骤如下:

首先,在res文件夹下的anim文件夹中建立一个set动画资源,本例中命名为listview_anim.xml,示例代码如下:

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha android:fromAlpha="0" android:toAlpha="1" android:duration="1000"/>
    <translate android:fromXDelta="1000" android:toXDelta="0" android:duration="1000"/>
</set>

这个动画的作用是让View从右侧飞入,且有一个由浅入深的渐变效果,每个动画持续1000ms。

然后,在anim文件夹下建立一个layoutAnimation资源,本例中命名为listview_layout_animation.xml,示例代码如下:

<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:delay="0.3"
    android:animationOrder="random"
    android:animation="@anim/listview_anim">
</layoutAnimation>

delay指定下一次动画相对上一次动画的延迟倍数,可以是0到1之间的值;animation指定需要使用的动画资源。animationOrder指定子View的动画执行顺序,可选值与含义如下:

  1. normal:ListView的列表项顺序执行动画(从第一个可见列表项开始执行到最后一个可见列表项)
  2. random:ListView的列表项随机执行动画
  3. reverse:ListView的列表项逆序执行动画(从最后一个可见列表项开始执行到第一个可见列表项)

最后,为ListView指定对应的layoutAnimation资源即可:

<ListView
    android:id="@+id/list_view_normal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layoutAnimation="@anim/listview_layout_animation"/>

提示:如果需要手动执行布局动画,可以调用ListView的startLayoutAnimation方法。

效果截图:

增加列表头和列表尾

相关方法:

//data:为HeaderView绑定的数据(可通过ListAdapter#getItem方法获得)
//isSelectable:指定HeaderView是否可选中(是否触发OnItemClickListener和OnItemLongClickListener)
public void addHeaderView(View v, Object data, boolean isSelectable);//添加指定列表头(可调用多次,从上往下逐次添加)
public void addHeaderView(View v);//添加指定列表头(可调用多次,从上往下逐次添加)
public int getHeaderViewsCount();//获得列表头的个数
public boolean removeHeaderView(View v);//移除列表头

//data:为FooterView绑定的数据(可通过ListAdapter#getItem方法获得)
//isSelectable:指定FooterView是否可选中((是否触发OnItemClickListener和OnItemLongClickListener))
public void addFooterView(View v, Object data, boolean isSelectable);//添加指定列表头(可调用多次,从上往下逐次添加)
public void addFooterView(View v);//添加指定列表头(可调用多次,从上往下逐次添加)
public int getFooterViewsCount();//获得列表尾的个数
public boolean removeFooterView(View v);//移除列表尾

说明:方法中的addHeaderView(View v)其实是通过调用addHeaderView(view, null, true)的方式实现的。addFooterView(View v)方法与之同理。

示例代码:

//为ListView添加列表头/尾
String[] dataArray=new String[]{"coding","ending","CodingEnding","Github","coder","Android"};
ArrayAdapter<String> headerFooterAdapter=new ArrayAdapter<String>(this,
        android.R.layout.simple_list_item_1,dataArray);
LayoutInflater inflater=LayoutInflater.from(this);
View headerView=inflater.inflate(R.layout.listview_header,headerFooterListView,false);//实例化头布局
View footerView=inflater.inflate(R.layout.listview_footer,headerFooterListView,false);//实例化尾布局
headerFooterListView.addHeaderView(headerView,"HeaderView",true);//设置列表头可选中
headerFooterListView.addFooterView(footerView,"FooterView",false);//设置列表尾不可选中
headerFooterListView.setAdapter(headerFooterAdapter);

效果截图:

提示: addFooterView和addHeaderView应该在ListView使用setAdapter设置适配器前调用,否则可能出现异常。

注意:如果为ListView设置了HeaderView或者FooterView,在OnItemClickListener的onItemClick方法中,position可能不是我们希望取得的值(因为算上了HeaderView和FooterView的个数)。此时,如果想要获得选中项,应该通过AdapterView#getAdapter获取,示例代码如下:

customListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Book book= (Book) parent.getAdapter().getItem(position);
    }
});

parent.getAdapter()获取的是ListView适配器的包装类,它的getItem方法会排除HeaderView和FooterView的影响,返回正确的对象。

动态增加列表项

先向数据源(如ArrayList)中添加数据,然后调用ListAdapter#notifyDataSetChanged方法通知列表刷新。示例代码如下:

List<Book> dataList dataList=new ArrayList<>();
.....
dataList.add(new Book("《新的书籍》",R.mipmap.ic_launcher));//为数据源新增数据
styleAdapter.notifyDataSetChanged();//通知ListView数据已更新

具体代码请参考demo。

设置列表的滚动模式

android:transcriptMode:设置列表的滚动模式

ListView的滚动模式由transcriptMode属性决定,它有三种可选值,含义如下:

  1. disabled:列表有新的数据增加时,ListView并不发生滚动。[默认值]
  2. normal:如果最后一个列表项在可视范围内,当有新的数据增加时,列表会自动滚动到底部。
  3. alwaysScroll:只要有新的数据增加时,列表就会自动滚动到底部。

在开发中根据实际需求选择相应的滚动模式即可。

跳转到指定位置

//跳转到指定位置(让这个Item成为列表当前的第一个可见项)
public void setSelection(int position);

//让HeaderView成为列表当前的第一个可见项(如果HeaderView不存在则显示position为0的项)
public void setSelectionAfterHeaderView();

小技巧:如果这两个方法在调用时无效,可以先调用ListView的clearFocus方法。

平滑滚动到指定位置

//平滑滚动到指定位置
public void smoothScrollToPosition(int position);

//平滑滚动n个列表项的距离
//offset:需要滚动的列表项个数(offset为正数时ListView向上滚动,为负数时向下滚动)
public void smoothScrollByOffset(int offset);

注意: smoothScrollToPosition并不保证将指定位置的列表项显示为列表当前的第一个可见项,只保证这个列表项在可视范围内。

效果截图:

监听滚动状态

只需要为ListView设置OnScrollListener即可,示例代码如下:

//监听ListView的滑动状态
normalListView.setOnScrollListener(new AbsListView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        //滑动状态发生变化时触发
        //scrollState的可能值:[SCROLL_STATE_IDLE|SCROLL_STATE_TOUCH_SCROLL|SCROLL_STATE_FLING]
    }
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        //滑动完成时触发
        //firstVisibleItem:第一个可见项的索引值
        //visibleItemCount:可见项的个数
        //totalItemCount:列表项的总数
    }
});

onScrollStateChanged中的scrollState可能有三种取值,含义如下:

  1. SCROLL_STATE_IDLE:静止状态
  2. SCROLL_STATE_TOUCH_SCROLL:滑动状态(用户此时触碰着屏幕且在滑动)
  3. SCROLL_STATE_FLING:惯性滑动状态(用户此时未触碰屏幕,ListView借助上一次滑动的惯性滑动)

小技巧:可在OnScrollListener的onScroll方法中判断ListView是否已经滑动到末尾,示例代码如下:

@Override
public void onScroll(AbsListView view,int firstVisibleItem,int visibleItemCount,int totalItemCount) {
    if(totalItemCount>0&&firstVisibleItem+visibleItemCount==totalItemCount){
        //已经滚动到末尾
    }
}

遍历列表当前所有的可见元素

for(int i=0;i<normalListView.getChildCount();i++){
    View view=normalListView.getChildAt(i);//可以强制转换为具体的View
}

常见问题

子控件抢夺焦点

问题描述:在自定义ListView列表项布局的时候,如果列表项中包含Button、CheckBox等需要焦点的控件,就可能导致点击列表项不起作用。

解决方案:

  1. 将列表项中Button、CheckBox等控件的android:focusable设置为false。
  2. 将列表项根布局的android:descendantFocusability属性设置为blocksDescendants。

descendantFocusability属性的可选值和效果说明如下:

  1. beforeDescendants:ViewGroup会在所有子View之前获得焦点
  2. afterDescendants:ViewGroup会在所有子View之后获得焦点
  3. blocksDescendants:ViewGroup会组织子View获得焦点

上面两种解决方案任选一种即可。

异步加载时图片显示错位

问题描述:如果列表项中的图片需要异步加载,由于ListView的View复用机制,在图片下载完毕时原来的ImageView可能已被复用,就可能导致图片显示错位。

解决方案:首先调用setTag方法为列表项中的ImageView设置标签。在异步加载完毕后,通过ListView的findViewWithTag方法查找ImageView。如果ImageView已经被复用了,这个方法的返回值就是null。通过判断这个方法的返回值是否为null,决定是否为ImageView设置图片资源,就可以解决图片显示错位的问题。示例代码如下:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    .....
    imageView.setTag(imageUrl);//将图片地址作为ImageView的Tag
    .....
}
ImageView imageView=listView.findViewWithTag(imageUrl);
if(imageView!=null){
    imageView.setImageDrawable(drawable);
}

更多博客

《 Android UI GridView讲解》:详细讲解GridView的使用方法和常用技巧。
《 Android UI 常用控件讲解》:包括CheckBox、RadioButton、ToggleButton、Switch、ProgressBar、SeekBar、RatingBar、Spinner、ImageButton。

demo下载地址

https://github.com/CodingEnding/UISystemDemo [ 持续更新中 ]

参考资料

http://gundumw100.iteye.com/blog/1169065
http://blog.csdn.net/guolin_blog/article/details/45586553
http://blog.csdn.net/yangshangwei/article/details/50322919
http://blog.csdn.net/csdn_aiyang/article/details/70739945
http://blog.csdn.net/zhuwentao2150/article/details/52425334
http://blog.csdn.net/quwei3930921/article/details/51013012

猜你喜欢

转载自blog.csdn.net/CodingEnding/article/details/79512087