关于 android listview 加载数据错位(错乱)问题

本文探讨了Android中ListView的数据加载及缓存机制,并通过具体案例解析了ListView数据错位的原因,提供了正确的解决方法。

一般的关于Adapter中getView的写法不外乎以下形式:

@Overridepublic 

ViewgetView(int position, View convertView, ViewGroup parent) 

{  

ViewHolder holder;  

if (convertView == null

{  

convertView = mLayout.inflate(R.layout....);  

holder =new ViewHolder();  

holder.textView = (TextView) convertView.findViewById(R.id.textview);  

... ...  

convertView.setTag(holder);  

else {  

holder = (ViewHolder) convertView.getTag();  

}  


holder.textView.setText(mText + position);  

return convertView;

}

在Android源码中关于getView方法的实现就是采用的以上形式,如ArrayAdapter等。因为这种写法的好处也是显而易见的,如果该position的convertview曾经被加载过,在数据集合未被改动的前提下,系统会自动将该position的convertview缓存起来,避免重复加载耗费资源。

我自己的代码:

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

    final ViewHolder mViewHolder;
    if(null == view){
        mViewHolder = new ViewHolder();
        view = LayoutInflater.from(mContext).inflate(R.layout.fragment_new_order_list_item, null);

        mViewHolder.txtPaystatus = (TextView) view.findViewById(R.id.order_pay_status);
        mViewHolder.txtOrdertime = (TextView) view.findViewById(R.id.order_time);
        mViewHolder.txtCustomerName = (TextView) view.findViewById(R.id.customer_name);
        mViewHolder.txtCustomerAddress = (TextView) view.findViewById(R.id.customer_address);
        mViewHolder.txtOrderSendTime = (TextView) view.findViewById(R.id.customer_post_time);
        mViewHolder.txtOrderGoodsDes = (TextView) view.findViewById(R.id.customer_list_goods_des);
        mViewHolder.txtCustomerPhone = (TextView) view.findViewById(R.id.customer_phone);
        mViewHolder.btnOrderOk = (Button) view.findViewById(R.id.order_ok);
        mViewHolder.btnOrderCancel = (Button) view.findViewById(R.id.order_cancel);
        mViewHolder.listgoods = (ListView) view.findViewById(R.id.customer_list_goods);

        if(listOrder.get(position).getPaystatus() == 0)
        {
            mViewHolder.txtPaystatus.setText("未付款");
        }
        else
        {
            mViewHolder.txtPaystatus.setText("已付款");
        }

        mViewHolder.txtOrdertime.setText(listOrder.get(position).getOrdertime());
        mViewHolder.txtCustomerName.setText(listOrder.get(position).getAcceptname());
        mViewHolder.txtCustomerAddress.setText(listOrder.get(position).getAcceptlocation());
        mViewHolder.txtOrderSendTime.setText(listOrder.get(position).getGoodsarrivetime());
        mViewHolder.txtOrderGoodsDes.setText(listOrder.get(position).getOrderSeller().getRemark());
        mViewHolder.txtCustomerPhone.setText(listOrder.get(position).getAcceptphonenum());
        OrderGoodsListItemAdapter mOrderGoodsListItemAdapter = new OrderGoodsListItemAdapter(mContext,
                listOrder.get(position).getOrderSeller().getLstOrderGoods());
        mViewHolder.listgoods.setAdapter(mOrderGoodsListItemAdapter);
        setListViewHeightOnChildren(mViewHolder.listgoods);
        final int index = position;
        mViewHolder.btnOrderOk.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                String sellerorderid = String.valueOf(listOrder.get(index).getOrderSeller().getSellerorderid());
                String token = App.getInstance().sellerInfo.getToken();
                orderService.modifyOrder(sellerorderid, "1", token)
                        .subscribe(new Action1<String>() {
                            @Override
                            public void call(String s) {
                                Toast.makeText(mContext, s, Toast.LENGTH_SHORT).show();
                                newOrderFragment.listOrder.clear();
                                newOrderFragment.pageindex = 1;
                                newOrderFragment.requestData();
                                App.isNeedFreshData = true;
                                App.isNeedFresShophData = true;

                            }
                        }, new Action1<Throwable>() {
                            @Override
                            public void call(Throwable throwable) {
                                Toast.makeText(mContext, throwable.getMessage(), Toast.LENGTH_SHORT).show();
                            }
                        });
            }
        });

        mViewHolder.btnOrderCancel.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                String sellerorderid = String.valueOf(listOrder.get(index).getOrderSeller().getSellerorderid());
                String token = App.getInstance().sellerInfo.getToken();
                orderService.modifyOrder(sellerorderid, "2", token)
                        .subscribe(new Action1<String>() {
                            @Override
                            public void call(String s) {
                                Toast.makeText(mContext, s, Toast.LENGTH_SHORT).show();
                                newOrderFragment.listOrder.clear();
                                newOrderFragment.pageindex = 1;
                                newOrderFragment.requestData();
                                App.isNeedFreshData = true;
                                App.isNeedFresShophData = true;
                            }
                        }, new Action1<Throwable>() {
                            @Override
                            public void call(Throwable throwable) {
                                Toast.makeText(mContext, throwable.getMessage(), Toast.LENGTH_SHORT).show();
                            }
                        });
            }
        });
        view.setTag(mViewHolder);
    }
    else
    {
        mViewHolder = (ViewHolder) view.getTag();
    }
    return view;
}

然后问题就来了,当时我就”自作小聪明“或者说“没有注意”,觉得当convertview==null时只是做了item布局的加载以及相关控件ID的绑定操作,为什么连内容的加载操作也放入其中呢,这样下次加载缓存是就省去内容set的操作了,然后就出现了滑动ListView后数据显示错位的问题。


剖析原因:

后来看源码发现,原来AbListView中获取getView()和滑动操作是异步进行的,其中滑动操作在一个FlingRunnable的支线程中运行,所以这就导致了在ListView在滑动时可能已经滑动到了第十行,但可能第二行的数据这时就被直接使用了,这就是导致数据加载错乱的根本原因。
附上源码中对FlingRunnable的注释:

/**
 * Responsible for fling behavior. Use {@link #start(int)} to
 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
 * A FlingRunnable will keep re-posting itself until the fling is done.
 *
 */
private class FlingRunnable implements Runnable {
    /**
     * Tracks the decay of a fling scroll
     */
    private final OverScroller mScroller;
    ... ...
}

解决方法

所以唯一的解决方法就是只在convertview中缓存该ChildView的layout,但ChildView 中的数据必须每次都重新获取并加载。

修改后的代码:

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

    final ViewHolder mViewHolder;
    if(null == view){
        mViewHolder = new ViewHolder();
        view = LayoutInflater.from(mContext).inflate(R.layout.fragment_new_order_list_item, null);

        mViewHolder.txtPaystatus = (TextView) view.findViewById(R.id.order_pay_status);
        mViewHolder.txtOrdertime = (TextView) view.findViewById(R.id.order_time);
        mViewHolder.txtCustomerName = (TextView) view.findViewById(R.id.customer_name);
        mViewHolder.txtCustomerAddress = (TextView) view.findViewById(R.id.customer_address);
        mViewHolder.txtOrderSendTime = (TextView) view.findViewById(R.id.customer_post_time);
        mViewHolder.txtOrderGoodsDes = (TextView) view.findViewById(R.id.customer_list_goods_des);
        mViewHolder.txtCustomerPhone = (TextView) view.findViewById(R.id.customer_phone);
        mViewHolder.btnOrderOk = (Button) view.findViewById(R.id.order_ok);
        mViewHolder.btnOrderCancel = (Button) view.findViewById(R.id.order_cancel);
        mViewHolder.listgoods = (ListView) view.findViewById(R.id.customer_list_goods);
        view.setTag(mViewHolder);
    }
    else
    {
        mViewHolder = (ViewHolder) view.getTag();
    }
    if(listOrder.get(position).getPaystatus() == 0)
    {
        mViewHolder.txtPaystatus.setText("未付款");
    }
    else
    {
        mViewHolder.txtPaystatus.setText("已付款");
    }

    mViewHolder.txtOrdertime.setText(listOrder.get(position).getOrdertime());
    mViewHolder.txtCustomerName.setText(listOrder.get(position).getAcceptname());
    mViewHolder.txtCustomerAddress.setText(listOrder.get(position).getAcceptlocation());
    mViewHolder.txtOrderSendTime.setText(listOrder.get(position).getGoodsarrivetime());
    mViewHolder.txtOrderGoodsDes.setText(listOrder.get(position).getOrderSeller().getRemark());
    mViewHolder.txtCustomerPhone.setText(listOrder.get(position).getAcceptphonenum());

    OrderGoodsListItemAdapter mOrderGoodsListItemAdapter = new OrderGoodsListItemAdapter(mContext,
            listOrder.get(position).getOrderSeller().getLstOrderGoods());
    mViewHolder.listgoods.setAdapter(mOrderGoodsListItemAdapter);
    setListViewHeightOnChildren(mViewHolder.listgoods);
    final int index = position;
    mViewHolder.btnOrderOk.setOnClickListener(new View.OnClickListener(){
        @Override
        public void onClick(View v) {
            String sellerorderid = String.valueOf(listOrder.get(index).getOrderSeller().getSellerorderid());
            String token = App.getInstance().sellerInfo.getToken();
            orderService.modifyOrder(sellerorderid, "1", token)
                    .subscribe(new Action1<String>() {
                        @Override
                        public void call(String s) {
                            Toast.makeText(mContext, s, Toast.LENGTH_SHORT).show();
                            newOrderFragment.listOrder.clear();
                            newOrderFragment.pageindex = 1;
                            newOrderFragment.requestData();
                            App.isNeedFreshData = true;
                            App.isNeedFresShophData = true;
                        }
                    }, new Action1<Throwable>() {
                        @Override
                        public void call(Throwable throwable) {
                            Toast.makeText(mContext, throwable.getMessage(), Toast.LENGTH_SHORT).show();
                        }
                    });
        }
    });

    mViewHolder.btnOrderCancel.setOnClickListener(new View.OnClickListener(){
        @Override
        public void onClick(View v) {
            String sellerorderid = String.valueOf(listOrder.get(index).getOrderSeller().getSellerorderid());
            String token = App.getInstance().sellerInfo.getToken();
            orderService.modifyOrder(sellerorderid, "2", token)
                    .subscribe(new Action1<String>() {
                        @Override
                        public void call(String s) {
                            Toast.makeText(mContext, s, Toast.LENGTH_SHORT).show();
                            newOrderFragment.listOrder.clear();
                            newOrderFragment.pageindex = 1;
                            newOrderFragment.requestData();
                            App.isNeedFreshData = true;
                            App.isNeedFresShophData = true;
                        }
                    }, new Action1<Throwable>() {
                        @Override
                        public void call(Throwable throwable) {
                            Toast.makeText(mContext, throwable.getMessage(), Toast.LENGTH_SHORT).show();
                        }
                    });
        }
    });
    return view;
}

其实ListView数据加载及数据缓存是比较复杂的,所以以后有机会还是要好好研读源码,这样才能有助于提升自己开发Android的性能和对Android工作的原理的理解。

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值