【Android学习】列表(ListView、RecyclerView)和adapter

1,ListView

1)概念

①ListView 显示数据的原理: mvc 模式
m:mode 数据 (用 javabean 规范封装)
v:view ListView
c:adapter 适配器, 负责把数据展示到 ListView 上

2)实现

①显示数据的步骤

创建 ListView->MAdapter extends BaseAdapter->
绑定:listView.setAdapter(adapter);

//通知listview更新数据
adapter.notifyDataSetChanged();

②设置分割线

listView布局添加如下属性:

android:divider="#FFF"        
android:dividerHeight="1px"   

去掉分割线:

//方案一:设置
android:divider="@null" 
//方案二:
android:divider="#00000000"
//方案三:高度设为0
.setDividerHeight(0)

③设置默认选中

setAdapter() 其实是异步的 ,调用了这个方法, ListView 的 item 并没有立马创建,而是在下一轮消息处理时才创建。所以不能直接使用setItemChecked方法,使用 post() 提交一个 Runnable() 对象,在 Runnable() 内部来做默认选中这种初始化动作。

actionList.post(new Runnable() {
            @Override
            public void run() {
                lastCheckedOption = actionList.getChildAt(1).findViewById(R.id.recharge_method_checked);
                lastCheckedOption.setVisibility(View.VISIBLE);
                actionList.setItemChecked(1, true);
            }
        });

④listview.setselection(position)

将列表移动到指定的Position处。

⑤获取item中的控件id

ImageView oldimg=(ImageView) listview.getChildAt(you_want_get_position).findViewById(R.id.item);

⑥item点击问题

i>item点击没有用
可能是item上的控件获得了点击事件,可以通过对Item Layout的根控件设置以下属性,屏蔽了所有子控件获取Focus的权限。

android:descendantFocusability="blocksDescendants"

⑦item点击事件

 mylistview.setOnItemClickListener(new OnItemClickListener(){
 
            @Override
            public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
                    long arg3) {
            }             
        });

3)性能问题

①ListView 内存溢出

尽量复用convertview历史的缓存,减少创建新的view对象。
尽量的减少子孩子的id的查询次数,定义一个viewHolder。

②ViewHolder

listView滚动的时候可以快速设置值,而不必每次都重新创建很多对象(从xml中反复生成View会造成OOM),避免了每次从布局文件中拿到View,从而提升性能。
当所有Item展示不满一页,就没必要使用ViewHolder方案。

ViewHolder对消失在屏幕的 View 进行缓存,当往下拖动时复用历史缓存,这样就只创建屏幕能显示的 View 个数的数量的 View。
(比如:一页有4个item。下滑时,item1划出屏幕被存入Recycler(反复循环器)构件,Recycler(反复循环器)构件将View放到item5的位置,进入屏幕。)

原理1:

getView 方法有个参数 convertView 就是可以复用的历史缓存 View 对象。这个对象也可能为空,当它为空的时候,表示该条目 view 第一次创建,所以我们需要 inflate 一个 view 出来。

原理2:

Android 提供了一个叫做 Recycler(反复循环) 的构件,就是当 ListView 的 Item 从滚出屏幕视角之外,对应 Item 的 View 会被缓存到 Recycler 中,相应的会从生成一个 Item,而此时调用的 getView 中的 convertView 参数就是滚出屏幕的缓存 Item 的 View,所以说如果能重用这个 convertView,就会大大改善性能。

原理3:

在 getView() 方法中的操作是先从 xml 中创建 view 对象(inflate 操作,我们采用了重用 convertView 方法优化),
在适配器中增加ViewHolder类,作为一个临时储存器,把getView方法中每次返回的View存起来,可以下次使用。

在 convertView 为 null 的时候,我们不仅重新 inflate 出来一个 view,并且还需要进行 findviewbyId 的查找工作,但是同时我们还需要获取一个 ViewHolder 类的对象,并将 findviewById 的结果赋值给 ViewHolder 中对应的成员变量。最后将 holder 对象与该 view 对象「绑」在一块。当 convertView 不为 null 时,我们让 view=converView,同时取出这个 view 对应的 holder 对象,就获得了这个 view 对象中的子控件,就是 holder 中的成员变量,这样在复用的时候,我们就不需要再去 findViewById 了,只需要在最开始的时候进行数次查找工作就可以了。这里的关键在于如何将 view 与 holder 对象进行绑定,那么就需要用到两个方法:View 中的 setTag 和 getTag 方法了。

③网络数据过多加载缓慢、内存溢出

问题:
ListView 如果显示本地的 List 集合中的内容,List 的长度也只有 100 个,我们可以毫不费力一次性加载完这 100 个数据;
实际应用中,我们往往会需要使用 Listview 来显示网络上的内容,在网络不流畅的情况下,用户加载完所有网络的数据,可能这个 list 是 1000 条数据,那么用户可能需要面对一个空白的 Activity 好几分钟,这个显然是不合适的。

解决:
分批加载。一次加载 20 条,等到用户翻页到底部的时候,我们再添加下面的 20 条到 List 中,再使用 Adapter 刷新 ListView,这样用户一次只需要等待 20 条数据的传输时间,不需要一次等待好几分钟把数据都加载完再在 ListView 上显示。其次这样也可以缓解很多条数据一次加载进行产生 OOM 应用崩溃的情况。

用分批加载,最终所有数据加载完后,list会很长,可能出现内存溢出从而导致应用崩溃。故此时可以采用分页加载来解决。

分页加载。把1000条数据分成10页,每页100条数据,每一页加载时都覆盖掉上一页中list集合中的内容,然后每一页内容再使用分批加载,这样用户体验会更好。

详细可查看下述中,下拉刷新的列表案例。

4)RecycleBin机制

①概念

Adapter是数据源,AdapterView是展示数据源的UI控件,通过调用AdapterView的setAdapter方法就可以让一个AdapterView绑定Adapter对象,从而AdapterView会将Adapter中的数据展示出来。

AdapterView的子类有AbsListView和AbsSpinner等,其中AbsListView的子类又有ListView、GridView等,所以ListView继承自AdapterView。

RecycleBin机制是ListView能够实现成百上千条数据都不会OOM最重要的一个原因。
Android在设计ListView这个类的时候,引入了RecycleBin机制—–对子View进行回收利用,RecycleBin直译过来就是回收站的意思。

②原理

RecycleBin中有两个重要的View数组,分别是mActiveViews和mScrapViews。这两个数组中所存储的View都是用来复用的,只不过mActiveViews中存储的是OnScreen的View,这些View很有可能被直接复用;而mScrapViews中存储的是OffScreen的View,这些View主要是用来间接复用的。

5)分页加载

实现OnScrollListener 接口重写onScrollStateChanged 和onScroll方法,使用onscroll方法实现”滑动“后处理检查是否还有新的记录,如果有,调用 addFooterView,添加记录到adapter, adapter调用 notifyDataSetChanged 更新数据;如果没有记录了,把自定义的mFooterView去掉。使用onScrollStateChanged可以检测是否滚到最后一行且停止滚动然后执行加载

2,ExpandableListView(可展开的列表组件)

1)概念

是一个垂直滚动的显示两个级别(Child.Group)列表项的视图,列表项来自ExpandableListView,可展示二级列表。

2)场景

由于双层ListView会造成无法获取焦点问题,故用ExpandableListView替代。
但双层RecyclerView更加灵活。

3)方法

①首次加载全部展开

mDownloadListView.setAdapter(mDownloadAdapter); 
for (int i = 0; i < downloadGroup.size(); i++) { 
mDownloadListView.expandGroup(i); 
} 

②不能点击收缩

mDownloadListView.setOnGroupClickListener(new OnGroupClickListener() {

        @Override
        public boolean onGroupClick(ExpandableListView parent, View v,
                int groupPosition, long id) {
            return true;
        }
    });

3,RecyclerView

1)概念

RecyclerView 是Android L版本中新添加的一个用来取代ListView的SDK,它的灵活性与可替代性比listview更好。
RecyclerView可以通过导入support-v7对其进行使用。
该控件用于在有限的窗口中展示大量数据集,其实这样功能的控件我们并不陌生,例如:ListView、GridView。
RecyclerView的任务就是回收和定位屏幕上的View。

2)优点

提供了一种插拔式的体验,高度的解耦,异常的灵活,通过设置它提供的不同LayoutManager,ItemDecoration , ItemAnimator。

3)方法

控制其显示的方式:布局管理器LayoutManager
控制Item间的间隔(可绘制):通过ItemDecoration
控制Item增删的动画:通过ItemAnimator

mRecyclerView = findView(R.id.id_recyclerview);
//设置布局管理器
mRecyclerView.setLayoutManager(layout);
//设置adapter
mRecyclerView.setAdapter(adapter)
//设置Item增加、移除动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
//添加分割线
mRecyclerView.addItemDecoration(new DividerItemDecoration(
                getActivity(), DividerItemDecoration.HORIZONTAL_LIST));
//滚动到指定位置
recyclerView.scrollToPosition(position);//直接跳过去
recyclerView.smoothScrollToPosition(position);//平稳划过去
recyclerView.scrollBy(x, y);//这个方法是自己去控制移动的距离,单位是像素,需要自己去计算移动的高度或宽度。

4)实现

①xml

添加依赖:

compile 'com.android.support:recyclerview-v7:21.0.+'

布局文件:

    <android.support.v7.widget.RecyclerView
        android:id="@+id/RecyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

②设置Adapter

public class AddressAdapter extends RecyclerView.Adapter<AddressAdapter.ViewHolder>{
    private Context mContext;
    private List<Address> addressList;

    public AddressAdapter(List<Address> addressList) {
        this.addressList = addressList;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        mContext = parent.getContext();
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_act_address, parent, false);
        return new ViewHolder(view);
    }

    //将数据与界面进行绑定的操作
    @Override
    public void onBindViewHolder(final ViewHolder holder, final int position) { 
	    holder.tv_addresssetText(addressList.get(position));       
    }
    //获取数据的数量
    @Override
    public int getItemCount() {
        return null != addressList ? addressList.size() : 0;
    }
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView tv_address;
        public ViewHolder(View view) {
            super(view);
            tv_address= (TextView) view.findViewById(R.id.tv_address);
        }
    }
}

③绑定控件并初始化

        recyclerView = (RecyclerView) findViewById(R.id.RecyclerView);
        LinearLayoutManager mLinearLayoutManager = new LinearLayoutManager(this);
        //设置滑动的方向        
        mLinearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);        
        recyclerView.setLayoutManager(mLinearLayoutManager);

        addressAdapter = new AddressAdapter(addressList);
        recyclerView.setAdapter(addressAdapter);

5)item点击事件

使用了观察者模式。

①定义接口

public interface OnItemClickListener {
    void onItemClick(View view , int position);
}

②Adapter中声明接口变量并暴露给外部调用者

    private OnItemClickListener onItemClickListener;
    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

③在Adapter的onBindViewHolder方法中添加点击事件

		//设置了tv_address的item的点击,供外部点击回调
        holder.tv_address.setTag(position);
        holder.tv_address.setOnClickListener(this);
        //item点击回调
        holder.item.setTag(position);
        holder.item.setOnClickListener(this);

④在Adapter中将点击事件转移给外部调用者

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.tv_address:
                if (onItemClickListener != null) {
                    //注意这里使用getTag方法获取position
                    onItemClickListener.onItemClick(view,(int)view.getTag());
                }
                break;
        }
    }

外部item点击调用:

addressAdapter.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(View parent, int position) {
                switch (parent.getId()){
                    case R.id.tv_address:
                        break;
                }
            }
        });

⑤点击事件不执行问题

第一种:
根item添加:

android:descendantFocusability="blocksDescendants"

这种方法同样可以解决的问题:双层RecyclerView中,子RecyclerView先获取焦点的问题。
第二种:
子RecyclerView获取焦点,父RecyclerView无法点击。
解决方案:传递点击事件。

6)Adapter多布局方式

应用一:列表为空时,显示数据为空的页面图片。
如下代码是笔者封装的一个基类:

注意:LayoutInflater.from(context).inflater(layout, parent, false);这个方法中,最后一个参数请写成false,若写成null,则只会生成view,不会设置LayoutParam。所以顶层控件的参数会失效。将错就错的做法是在布局里多嵌套一层,或在代码里给view setLayoutParam。

/**
 * Created by luo on 2018/1/26.
 * 列表为空时Adapter
 */

public abstract class BasalEmptyAdapter<T> extends RecyclerView.Adapter implements View.OnClickListener {
    private static final int VIEW_TYPE_COUNT = 2;
    protected Context context;
    private List<T> list;

    public BasalEmptyAdapter(List<T> list) {
        this.list = list;
    }

    /**
     * 获取RecyclerView本来的布局
     *
     * @return
     */
    protected abstract RecyclerView.ViewHolder getViewHolder(ViewGroup parent, int viewType);

    /**
     * 替换子类的 onBindViewHolder方法
     * @param holder
     * @param position
     */
    protected abstract void onBindViewHolderRecyclerView(RecyclerView.ViewHolder holder, final int position);

    /**
     * 设置空布局的提示文字
     * @return
     */
    protected abstract String setEmptyText();
    @Override
    public int getItemViewType(int position) {
        return null != list && 0 != list.size() ? 0 : 1;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        context = parent.getContext();
        switch (viewType) {
            case 0://列表显示
                return getViewHolder(parent, viewType);
            case 1://空数据显示
                return new ViewHolderEmpty(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycler_view_empty, parent, false));
        }
        return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
        int viewType = getItemViewType(position);
        switch (viewType) {
            case 0://列表显示
                onBindViewHolderRecyclerView(holder, position);
                break;
            case 1://空数据显示
                ((ViewHolderEmpty) holder).tv_empty.setText(setEmptyText());
                break;
        }
    }

    @Override
    public int getItemCount() {

        return null != list && list.size() > 0 ? list.size() : 1;
    }

    class ViewHolderEmpty extends RecyclerView.ViewHolder {

        @BindView(R.id.tv_item_recycler_view_empty)
        TextView tv_empty;

        ViewHolderEmpty(View view) {
            super(view);
            ButterKnife.bind(this, view);
        }
    }
}

布局文件:

<?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"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_item_recycler_view_empty"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="159dp"
        android:drawablePadding="15dp"
        android:drawableTop="@mipmap/icon_order_no"
        android:gravity="center_horizontal"
        android:textColor="@color/black"
        android:textSize="13sp" />
</LinearLayout>

应用二:实现下图布局:
城市索引界面

实现方式:
通过viewType实现。
ListView的getViewTypeCount方法是用来获取当前listview总共有多少种类型的布局,对于RecyclerView,替代方法为getItemViewType方法,在onCreateViewHolder(ViewGroup parent, int viewType)这里的第二个参数就是View的类型,可以根据这个类型判断去创建不同item的ViewHolder。

代码:

public class SelectedCityAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private static final int VIEW_TYPE_COUNT = 4;//多布局数量 
    private Context mContext;
    private List<String> cityList;

    public SelectedCityAdapter(List<String> cityList) {
        this.cityList = cityList;
    }
    @Override
    public int getItemViewType(int position) {
        return position < VIEW_TYPE_COUNT - 1 ? position : VIEW_TYPE_COUNT - 1;
    }
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        switch (viewType){
            case 0://定位布局
                return new ViewHolderLocationCity(mInflater.inflate(R.layout.item_act_city_list_location_city, parent, false));
            case 1://历史布局
                return new ViewHolderHistoryCity(mInflater.inflate(R.layout.item_act_city_list_history_city, parent, false));
            case 2://热门布局
                return new ViewHolderHotCity(mInflater.inflate(R.layout.item_act_city_list_hot_city, parent, false));
            case 3://全部城市布局
                return new ViewHolderAllCity(mInflater.inflate(R.layout.item_act_city_list_all, parent, false));
        }
        return  null;
    }

    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
        int viewType = getItemViewType(position);
        switch (viewType){
            case 0://定位  break;
            case 1://历史  break;
            case 2://热门  break;
            case 3://全部城市  break;
        }
    }
    @Override
    public int getItemCount() {
        return cityList != null ? cityList.size() + 3 : 3;
    }

    /**
     * 全部城市
     */
    public class ViewHolderAllCity extends RecyclerView.ViewHolder {

        public ViewHolderAllCity(View itemView) {
            super(itemView);
        }
    }
    /**
     * 定位
     */
    public class ViewHolderLocationCity extends RecyclerView.ViewHolder {

        public ViewHolderLocationCity(View itemView) {
            super(itemView);
        }
    }

    /**
     * 历史城市
     */
    public class ViewHolderHistoryCity extends RecyclerView.ViewHolder {
        public ViewHolderHistoryCity(View itemView) {
            super(itemView);
        }
    }

    /**
     * 热门城市
     */
    public class ViewHolderHotCity extends RecyclerView.ViewHolder {
        public ViewHolderHotCity(View itemView) {
            super(itemView);
        }
    }

    public void setOnCityClickListener(OnCityClickListener listener){
        this.onCityClickListener = listener;
    }
}

7)LayoutManager

RecyclerView 用LayoutManager来确定每一个item的排列方式。
回收或重用一个View的时候,LayoutManager会向适配器请求新的数据来替换旧的数据,这种机制避免了创建过多的View和频繁的调用findViewById方法(与ListView原理类似)。

①LinearLayoutManager(线性布局)

竖直列表:

		//设置滑动的方向
        LinearLayoutManager mLinearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerViewAllCity.setLayoutManager(mLinearLayoutManager);

横向列表

LinearLayoutManager mLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);

②GridLayoutManager(网格布局)

i>实现一
columNum表示一行显示几列。

GridLayoutManager mLayoutManager = new GridLayoutManager(context,columNum);
mRecyclerView.setLayoutManager(mLayoutManager);

ii>实现二:
getSpanSize返回值就是控制每行有几列的。
通过这个方法可以控制每一行显示不同的列数。

layoutManage.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                 @Override
                 public int getSpanSize(int position) {
                     return 0;
                 }
             });

③StaggeredGridLayoutManager(瀑布流布局)

横向列表:

int spanCount = 1; // 只显示一行
manager_groupBuying = new StaggeredGridLayoutManager(spanCount, StaggeredGridLayoutManager.HORIZONTAL);
        RView_groupBuying.setLayoutManager(manager_groupBuying);      

④按行存放列表

第一行存满放下一行。根据item长度自动判断每行放几个。
AS依赖:

 compile 'com.google.android:flexbox:0.3.0'

LayoutManager:

  FlexboxLayoutManager outerColorLayoutManager = new FlexboxLayoutManager(context);
        outerColorLayoutManager.setFlexWrap(FlexWrap.WRAP);
        outerColorLayoutManager.setFlexDirection(FlexDirection.ROW);
        outerColorLayoutManager.setAlignItems(AlignItems.STRETCH);
        outerColorLayoutManager.setJustifyContent(JustifyContent.FLEX_START);

8)获取item中的控件

①实现

 LinearLayout layout = (LinearLayout)list.getChildAt(i);// 获得子item的layout
 EditText et = (EditText) layout.findViewById(R.id.et);// 从layout中获得控件,根据其id(itme中的控件)
// EditText et = (EditText) layout.getChildAt(1)//或者根据位置,在这我假设TextView在前,EditText在后

②问题

private void getContent() {
    //从Adapter获取评论内容
    for (int i = 0; i < linearLayoutList.size(); i++) {
        LinearLayout layout = (LinearLayout)recyclerView.getChildAt(i); // 获得子item的layout
        EditText et_content = (EditText) layout.findViewById(R.id.et_item_act_comment_content);
        ;//评论内容
        commentBean.setItemContent(i, et_content.getText().toString().trim());
    }
}

在上述方法中,获得的layout有时为null。
为什么呢?因为Adapter的item复用机制,对于界面上不显示的item,其item被复用,故item为null。
③解决方案
每次初始化的时候 ,缓存itemView。
Adapter中写如下代码:

private List<LinearLayout> linearLayoutList = new ArrayList<>();//item集合

public List<LinearLayout> getLinearLayoutList() {
    return linearLayoutList;
}

onBindViewHolder中,把每个item加入linearLayoutList。
修改后的方法:(通过linearLayoutList获得每一个item,并进一步获得其子控件)

private void getContent() {
    //从Adapter获取评论内容
    List<LinearLayout> linearLayoutList = commentAdapter.getLinearLayoutList();
    for (int i = 0; i < linearLayoutList.size(); i++) {
        LinearLayout layout = linearLayoutList.get(i); // 获得子item的layout
        EditText et_content = (EditText) layout.findViewById(R.id.et_item_act_comment_content);
        ;//评论内容
        commentBean.setItemContent(i, et_content.getText().toString().trim());
    }
}

9)上拉刷新和下拉加载分页

RecyclerView没有此功能,但我们可以使用开源控件pullToRefreshLayout来实现分页加载的功能。也可以实现界面加载刷新效果。

点击查看GitHub

②使用

具体用法请查看GitHub。
取消上拉加载界面:

pullToRefreshLayout.setCanLoadMore(false);

④注意

开发者可以使用 PullToRefresh 对各种控件实现下拉刷新或者上拉加载以及可以自定义刷新和加载部分的视图。
目前支持:ScrollView,ListView,WebView,RecyclerView。
对于单个控件,需要使用pullToRefreshLayout的功能,外层可包裹ScrollView。

⑤空列表显示

该控件提供了app:view_empty="@layout/layout_empty"来实现。但是并不好用。列表为空时,也应该具备下拉刷新的功能。
好的处理方法:在adpater中,通过ItemVieyType来实现。

⑥原理

无论是下拉刷新还是上拉加载更多,原理都是在内容View(ListView、RecyclerView…)不能下拉或者上划时响应用户的触摸事件,在顶部或者底部显示一个刷新视图,在程序刷新操作完成后再隐藏掉。
使用组合View的方式,先定义一个LinearLayout,LinearLayout中包含两个元素,一个是ListView,一个是下拉头,并且让这两个元素纵向排列,初始时,让下拉头偏移出屏幕,这样我们就只能看见ListView,然后对ListView的Touch事件进行监听,当ListView滚动到顶部时,如果继续下拉并且松手的时候,下拉的距离已经能够将header完全显示出来,则证明是下拉刷新状态,可以根据这个做一些相应的操作,比如网络请求数据等等,如果下拉的距离不够将header完全显示出来,则松手后,隐藏header.

10)item分割线

①线性布局分割线

方案:直接在xml中设置。
由于分割线是重复使用的控件,请保持良好的编码习惯,使用公共的View.
xml代码:

    <View style="@style/line_thin" />

style代码:

    <style name="line_thin">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">0.5dp</item>
        <item name="android:background">@color/gray_light</item>
    </style>

②GridLayoutManager多行分割线

效果:靠近屏幕的两侧没有分割线,其他view之间有分割线。
使用方法:(见类注释)
注意:addItemDecoration请确保只调用了一次。调用多次会出现间隔不断变大的问题。
原因:分割线进行了重复绘制。

/**
 * Created by luo on 2018/1/26.
 * 使用方法:
 * recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
 * recyclerView.addItemDecoration(new SpacesItemDecoration(10, 10));
 * 注意:addItemDecoration只能调用一次。调用多次会出现间隔不断变大的问题。
 */
public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
    private int leftRight;
    private int topBottom;

    public SpacesItemDecoration(int leftRight, int topBottom) {
        this.leftRight = leftRight;
        this.topBottom = topBottom;
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
        //判断总的数量是否可以整除
        int totalCount = layoutManager.getItemCount();
        int surplusCount = totalCount % layoutManager.getSpanCount();
        int childPosition = parent.getChildAdapterPosition(view);
        if (layoutManager.getOrientation() == GridLayoutManager.VERTICAL) {//竖直方向的
            if (surplusCount == 0 && childPosition > totalCount - layoutManager.getSpanCount() - 1) {
                //后面几项需要bottom
                outRect.bottom = topBottom;
            } else if (surplusCount != 0 && childPosition > totalCount - surplusCount - 1) {
                outRect.bottom = topBottom;
            }
            if ((childPosition + 1) % layoutManager.getSpanCount() != 1) {//每一行第一个去掉左边(不被整除的)
                outRect.left = leftRight;
            }
            outRect.top = topBottom;
        } else {
            if (surplusCount == 0 && childPosition > totalCount - layoutManager.getSpanCount() - 1) {
                //后面几项需要右边
                outRect.right = leftRight;
            } else if (surplusCount != 0 && childPosition > totalCount - surplusCount - 1) {
                outRect.right = leftRight;
            }
            if ((childPosition + 1) % layoutManager.getSpanCount() == 0) {//被整除的需要下边
                outRect.bottom = topBottom;
            }
            outRect.top = topBottom;
            outRect.left = leftRight;
        }
    }
}

11)滑动冲突问题

①RecyclerView和ScrollView滑动冲突

recyclerView.setNestedScrollingEnabled(false);

4,下拉刷新的列表

1)PullToRefreshExpandableListView 为双层列表。

①主页面实现OnRefreshListener2<ExpandableListView>接口

		elv_list.setOnRefreshListener(this);
		elv_list.getRefreshableView().setGroupIndicator(null);//取消前面箭头图标
		elv_list.getRefreshableView().setDivider(null);//取消底下黑线
	    // 设置下拉刷新文本  
		elv_list.setMode(Mode.BOTH);	
		elv_list.getLoadingLayoutProxy(false, true).setPullLabel("加载更多...");  
		elv_list.getLoadingLayoutProxy(false, true).setReleaseLabel("放开以加载...");  
		elv_list.getLoadingLayoutProxy(false, true).setRefreshingLabel("加载中..."); 
		
	@Override
	public void onPullDownToRefresh(
			PullToRefreshBase<ExpandableListView> refreshView) {
		// TODO Auto-generated method stub
		 if (elv_dynamic_list.isRefreshing()) {
			 thisPage_no = 1;
			 fList.clear();
			 getData();        
		}else {
			elv_dynamic_list.onRefreshComplete();
		}
	}

	@Override
	public void onPullUpToRefresh(
			PullToRefreshBase<ExpandableListView> refreshView) {
		// TODO Auto-generated method stub
		 if (elv_dynamic_list.isRefreshing()) {
			 thisPage_no ++;
			 getData();        
		}else {
			elv_dynamic_list.onRefreshComplete();
		}
	}

②列表使用PullToRefreshExpandableListView

	<com.handmark.pulltorefresh.library.PullToRefreshExpandableListView
        android:id="@+id/elv_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none"
        android:divider="#000"
        android:listSelector="@android:color/transparent"
         >
    </com.handmark.pulltorefresh.library.PullToRefreshExpandableListView>

③Adapter使用BaseExpandableListAdapter

public class DynamicAdapter extends BaseExpandableListAdapter{

	 private Context context;
	 private LayoutInflater lif;
	 private List<String> fList;
	 private MyInterface listener;//接口回调

	 
	 public DynamicAdapter(Context context,List<String> fList, MyInterface listener) {
		this.context = context;
		this.fList = fList;
		lif = LayoutInflater.from(context);
		this.listener = listener;
	}
	 
	 public void notifyDataSetChangedLists(){
		 notifyDataSetChanged();
	 }
	 
	@Override
	public int getGroupCount() {
		return fList == null ? 0 : fList.size();
	}

	@Override
	public int getChildrenCount(int groupPosition) {
		return 1;
	}

	@Override
	public Object getGroup(int groupPosition) {
		return fList.get(groupPosition);
	}

	@Override
	public Object getChild(int groupPosition, int childPosition) {
		return null;
	}

	@Override
	public long getGroupId(int groupPosition) {
		return groupPosition;
	}

	@Override
	public long getChildId(int groupPosition, int childPosition) {
		return childPosition;
	}	

	@Override
	public boolean hasStableIds() {
		return false;
	}

	@Override
	public View getGroupView(final int groupPosition, boolean isExpanded,
			View convertView, ViewGroup parent) {
		GroupViewHolder gvh = null;
		if (convertView == null) {
			convertView = lif.inflate(R.layout.elv_group, null);
			gvh = new GroupViewHolder();
			gvh.ll_elv_group = (LinearLayout) convertView.findViewById(R.id.ll_elv_group);
			gvh.focusImageView = (ImageView) convertView.findViewById(R.id.iv_focus);
			
			convertView.setTag(gvh);
		}else{
			gvh = (GroupViewHolder) convertView.getTag();
		}
		
		gvh.focusImageView.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				listener.onclickFocus(groupPosition);
			}
		});
		return convertView;
	}

	@Override
	public View getChildView(int groupPosition, int childPosition,
			boolean isLastChild, View convertView, ViewGroup parent) {
		convertView = null;
		ChildrenViewHolder cvh = null;
		if (convertView == null) {
			convertView = lif.inflate(R.layout.elv_children, null);
			cvh = new ChildrenViewHolder();

			cvh.textViewChild= (TextView)convertView.findViewById(R.id.tv);
			
			convertView.setTag(cvh);
		}else {
			cvh = (ChildrenViewHolder) convertView.getTag();
		}
		return convertView;
	}

	@Override
	public boolean isChildSelectable(int groupPosition, int childPosition) {
		return false;
	}

	public class GroupViewHolder{
		public ImageView focusImageView ;
	}
	
	public class ChildrenViewHolder{
		public TextView textViewChild;
	}
	
	public interface MyInterface{//接口回调
	    public void onclickFocus(int groupPosition);
	}
}

2)PullToRefreshListView,点击可跳转到下一页

为双层列表。
①主界面使用PullToRefreshListView
下拉刷新和上拉加载分页的功能和PullToRefreshExpandableListView一样。
点击事件:

rlview.setOnItemClickListener(new OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView<?> parent, View view,
					int position, long id) {
				Intent intent = new Intent(mContext,DetailActivity.class);
				startActivity(intent);
			}
		});

②Adapter使用BaseAdapter
使用和简单的自定义Adapter一致。

5,其他列表

1)ListActivity

ListActivity和Activity是同一级别的类。
ListActivity可以不用setContentView(R.layout.main),它默认是LIstView占满屏。

6,Adapter

Adapter是连接后端数据和前端显示的适配器接口。

常见的Adapter:

1)BaseAdapter

基础数据适配器。继承自接口类Adapter。
它的主要用途是将一组数据传到像ListView、Spinner、Gallery及GridView等UI显示组件。

2)ArrayAdapter

ArrayAdapter是BaseAdapter的派生类。
可以直接使用泛型构造。
ArrayAdapter的ListView被数组所持,这个数组可以装任意对象。

3)CursorAdapter

继承于BaseAdapter的一个虚类。
为Cursor和ListView连接提供了桥梁

4)HeaderViewListAdapter

HeaderViewListAdapter(ArrayList headerViewInfos, ArrayList footerViewInfos, ListAdapter adapter)

headerViewInfos 用于提供列表头
footerViewInfos 用于提供列表尾

5)ListAdapter

6)ResourceCursorAdapter

7)SimpleAdapter

一个使静态数据和在XML中定义的View对应起来的简单Adapter。
可以把list上的数据指定为一个Map泛型的ArrayList。ArrayList里的每一个条目对应于List里的一行。Map包含每一行的数据。

①概念

②支持组件

支持三种类型的 View,否则会抛出IllegalStateExeception.
i>实现了Checkable接口
如:CompoundButton
ii>TextView
iii>ImageView

8)SimpleCursorAdapter

用于对数据库进行操作。

9)SpinnerAdapter

WrapperListAdapter

7,demo

1)ListView&BaseAdapter

xml:<ListView/>、Adapter的内部item布局。
页面调用显示

	private ListView mListView;
	private InitAdapter mInitAdapter;
	private ArrayList<String> mArrayList;
	
	private void init(){

	    mListView=(ListView) findViewById(R.id.listView);
	    mArrayList = new ArrayList<String>();
	    mArrayList.add("张三1");
	    mArrayList.add("李四1");
	    mArrayList.add("王五1");
	    mInitAdapter = new InitAdapter(this, mArrayList);
	    mListView.setAdapter(mInitAdapter);
	}

自定义Adapter:

public class InitAdapter extends BaseAdapter{

    public List<String> list;
    private LayoutInflater mInflater;
    private Context mContext;


    public InitAdapter(Context context, List<String> list) {
        this.list = list;
        mInflater = LayoutInflater.from(context);
        mContext = context;
    }
    @Override
    public int getCount() {
        return list == null ? 0 : list.size();
    }

    @Override
    public Object getItem(int position) {
        if (list == null)
            return null;
        return list.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        View itemView = convertView;
        ViewHolder viewHolder = null;
        if (itemView != null) {
            viewHolder = (ViewHolder) itemView.getTag();
        } else {
            itemView = mInflater.inflate(R.layout.listviewitem, parent, false);
            viewHolder = new ViewHolder();
            findView(viewHolder, itemView);
            itemView.setTag(viewHolder);
        }

        if (list != null) {

            String item = this.list.get(position);
            if (viewHolder.textView != null) {
                initView(viewHolder, item);
            }
        }
        return itemView;
    }
    /**
     * 绑定View的控件
     * 
     * @param view
     */
    private void findView(ViewHolder viewHolder, View itemView) {

        viewHolder.textView = (TextView) itemView.findViewById(R.id.textView);          
    }

    /**
     * 设置View显示信息
     */
    private void initView(ViewHolder viewHolder, String item) {

         viewHolder.textView.setText(item);  
    }

    private class ViewHolder {

        TextView textView;
    }
}

2)

8,filter

1)概念

public abstract class Filter extends Object.

Java.lang.Object
	 Android.widget.Filter

过滤器通过过滤模式来约束数据,通常由实现了Filterable接口的子类来生成。 过滤操作是通过调用 filter(CharSequence) 或者 filter(CharSequence, android.widget.Filter.FilterListener)这些异步方法来完成的。以上方法一旦被调用,过滤请求就会被递交到请求队列中等待处理,同时该操作会取消那些之前递交的但是还没有被处理的请求。

2)方法

protected abstract Filter.FilterResults performFiltering (CharSequence constraint)

根据约束条件调用一个工作线程过滤数据。子类必须实现该方法来执行过滤操作。过滤结果以Filter.FilterResults的形式返回,然后在UI线程中通过publishResults(CharSequence,android.widget.Filter.FilterResults)方法来发布。

 protected abstract void publishResults (CharSequence constraint, Filter.FilterResults results)

通过调用UI线程在用户界面发布过滤结果。子类必须实现该方法来显示performFiltering(CharSequence)的过滤结果。

3)应用

①写一个继承Filter的类(内部类即可)

②写该类的过滤规则,重写performFiltering、publishResults方法。

demo

public class FilterAdapter extends BaseAdapter {	
	
	private NameFilter mNameFilter;
	private List<String> mArrayList;
	private List<String> mFilteredArrayList;
	private LayoutInflater mLayoutInflater;
  
	public FilterAdapter(Context context,List<String> arrayList) {
	    mArrayList = arrayList;
	    mLayoutInflater=LayoutInflater.from(context);
	    mFilteredArrayList=new ArrayList<String>();
	}

  @Override
  public int getCount() {
    if (mArrayList == null) {
      return 0;
    } else {
      return (mArrayList.size());
    }

  }

  @Override
  public Object getItem(int position) {
    if (mArrayList == null) {
      return null;
    } else {
      return mArrayList.get(position);
    }
  }

  @Override
  public long getItemId(int position) {
    return position;
  }
  
  public Filter getFilter() {  
       if (mNameFilter == null) {    
    	   mNameFilter = new NameFilter();    
       }    
      return mNameFilter;  
    }  


  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    View itemView = null;
    itemView = convertView;
    ViewHolder viewHolder = null;
    if (itemView == null) {
      itemView = mLayoutInflater.inflate(R.layout.listviewitem, null);
      viewHolder = new ViewHolder();
      viewHolder.textView = (TextView) itemView.findViewById(R.id.textView);
      itemView.setTag(viewHolder);
    } else {
      viewHolder = (ViewHolder) itemView.getTag();
    }

    if (mArrayList != null) {
      if (viewHolder.textView != null) {
        viewHolder.textView.setText((mArrayList.get(position)));
      }

    }

    convertView = itemView;
    return convertView;
  }
  

  private class ViewHolder {
    TextView textView;
  }
  
  
  	//过滤数据
  	class NameFilter extends Filter {
  		//执行筛选
	    @Override
	    protected FilterResults performFiltering(CharSequence charSequence) {
	    	
	    	FilterResults filterResults = new FilterResults();
	    	for (Iterator<String> iterator = mArrayList.iterator(); iterator.hasNext();) {
	    		
	    		String name = iterator.next();
	    		System.out.println("---> name=" + name);
	    		if (name.contains(charSequence)) {
	    			mFilteredArrayList.add(name);
	    		}
	    	}
	    	filterResults.values = mFilteredArrayList;
	      
	    	return filterResults;
	    }

	    //筛选结果
	    @Override
	    protected void publishResults(CharSequence arg0, FilterResults results) {
	      
	    	mArrayList = (List<String>) results.values;
	    	if (results.count > 0) {
	    		notifyDataSetChanged();
	    	} else {
	    		notifyDataSetInvalidated();
	    	}
	    }
    }
  
}
public class MainActivity extends Activity {

	    private ListView mListView;
	    private FilterAdapter mListViewAdapter;
	    private ArrayList<String> mArrayList;
//	    private HashMap<String, String> mHashMap;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		init();
	}
	private void init(){

	    mListView=(ListView) findViewById(R.id.listView);
	    mArrayList=new ArrayList<String>();
	    mArrayList.add("张三1");
	    mArrayList.add("李四1");
	    mArrayList.add("王五1");
	    mListViewAdapter=new FilterAdapter(this, mArrayList);
	    //过滤数据
	    //过滤出姓名里面包含"张"的数据并显示
	    mListViewAdapter.getFilter().filter("张");
	    mListView.setAdapter(mListViewAdapter);
	  }
}

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值