繁体   English   中英

使用什么设计概念来更新UI异步

[英]What design concept to use to update the UI async

我正在开发一个在时间轴上显示工作时间表的应用程序。

这是目前应用程序设计的粗略布局:

在此处输入图片说明

数据存储在SQLite数据库中。 Timeline (单例对象)从数据库帮助器类请求数据时,它将获得Event的ArrayList(例如,从2016年5月1日03:00开始,到5月3日结束,可能是一个Event任务。 2016年16:00)。 然后, Timeline将这些Event转换为TimelineItemTimelineItem是一个类,表示特定日期的Event (一部分)。

Event的加载和EventTimelineItem的转换都在AsyncTasks中完成。 到目前为止,一切都很好。

现在是我苦苦挣扎的部分:在获取新的数据库后更新UI。

我的第一种方法是将更新的TimelineItems ArrayList传递给RecyclerView适配器,并通过notifyDatasetChanged()让适配器知道数据已更改。 这种方法的问题是:1)正在完成许多不必要的工作(因为我们正在重新计算所有的Event / TimelineItems,而不仅是已更改的事件/ TimelineItems),以及2)每次提取数据库后, RecyclerView上的滚动位置都会重置

在第二种方法中,我实现了一些方法来检查自上次显示以来哪些Event / TimelineItems已更改,其思想是使用notifyItemChanged()仅更改那些TimelineItems。 工作量减少,完全无需担心滚动位置。 棘手的是,检查哪些项目已更改确实需要一些时间,因此也需要异步完成:

我试图通过在onProgressUpdate()发布otto bus事件来进行doInBackground()的代码操作和UI更新。

private class InsertEventsTask extends AsyncTask<Void, Integer, Void> {

    @Override
    protected Void doInBackground(Void... params) {
        ArrayList<Event> events = mCachedEvents;

        // if mChangedEvents is not null and not empty
        if (events != null && !events.isEmpty()) {
            // get the list of pairs for the events
            ArrayList<TimelineItemForDateTimePair> listOfPairs = convertEventsToPairs(events);
            // insert the TimelineItems from the pairs into the Timeline
            for (int i = 0; i < listOfPairs.size(); i++) {
                // get the last position for the DateTime associated with the pair
                int position = findLastPositionForDate(listOfPairs.get(i).dateTime);
                // if position is -1, the events started on a day before the timeline starts
                // so keep skipping pairs until position > -1
                if (position > -1) {
                    // if the item is a PlaceholderItem
                    if (mTimelineItems.get(position).isPlaceholderItem) {
                        // remove the PlaceholderItem
                        mTimelineItems.remove(position);
                        // and add the TimelineItem from the pair at the position the PlaceholderItem was at
                        mTimelineItems.add(position, listOfPairs.get(i).timelineItem);
                        // update the UI on the UI thread
                        publishProgress(position, TYPE_CHANGED);
                    } else { // if the item is not a PlaceholderItem, there's already an normal TimelineItem in place
                        // place the new item at the next position on the Timeline
                        mTimelineItems.add(position + 1, listOfPairs.get(i).timelineItem);
                        publishProgress(position, TYPE_ADDED);
                    }
                }
            }
        }
        return null;
    }

    /**
     * onProgressUpdate handles the UI changes on the UI thread for us. Type int available:
     * - TYPE_CHANGED
     * - TYPE_ADDED
     * - TYPE_DELETED
     *
     * @param values value[0] is the position as <code>int</code>,
     *               value[1] is the type of manipulation as <code>int</code>
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        int position = values[0];
        int type = values[1];

        // update the UI for each changed/added/deleted TimelineItem
        if (type == TYPE_CHANGED) {
            BusProvider.getInstance().post(new TimelineItemChangedNotification(position));
        } else if (type == TYPE_ADDED) {
            BusProvider.getInstance().post((new TimelineItemAddedNotification(position)));
        } else if (type == TYPE_DELETED) {
            // TODO: make delete work bro!
        }
    }
}

问题是,以某种方式在发布此进度时滚动会完全弄乱UI。

我的主要问题是:当我更新适配器的数据集(TimelineItems)中的特定项目时,notifyItemChanged()确实会更改该项目,但没有将该项目放在正确的位置。

这是我的适配器:

/**
* A custom RecyclerView Adapter to display a Timeline in a TimelineFragment.
*/
public class TimelineAdapter extends RecyclerView.Adapter<TimelineAdapter.TimelineItemViewHolder> {

/*************
 * VARIABLES *
 *************/

private ArrayList<TimelineItem> mTimelineItems;

/****************
 * CONSTRUCTORS *
 ****************/

/**
 * Constructor with <code>ArrayList<TimelineItem></code> as data set argument.
 *
 * @param timelineItems ArrayList with TimelineItems to display
 */
public TimelineAdapter(ArrayList<TimelineItem> timelineItems) {
    this.mTimelineItems = timelineItems;
}

// Create new views (invoked by the layout manager)
@Override
public TimelineItemViewHolder onCreateViewHolder(ViewGroup parent,
                                                 int viewType) {
    // create a new view
    View v = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.item_timeline, parent, false);
    // set the view's size, margins, paddings and layout parameters
    // ...

    return new TimelineItemViewHolder(v);
}

// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(TimelineItemViewHolder holder, int position) {
    // - get element from your data set at this position
    // - replace the contents of the view with that element

    // if the item is a ShowPreviousMonthsItem, set the showPreviousMonthsText accordingly
    if (mTimelineItems.get(position).isShowPreviousMonthsItem) {
        holder.showPreviousMonthsText.setText(mTimelineItems.get(position).showPreviousMonthsText);
    } else { // otherwise set the showPreviousMonthsText blank
        holder.showPreviousMonthsText.setText("");
    }

    // day of month & day of week of the TimelineItem
    if (mTimelineItems.get(position).isFirstItemOfDay) {
        holder.dayOfWeek.setText(mTimelineItems.get(position).dayOfWeek);
        holder.dayOfMonth.setText(mTimelineItems.get(position).dayOfMonth);
    } else {
        holder.dayOfWeek.setText("");
        holder.dayOfMonth.setText("");
    }

    // Event name for the TimelineItem
    holder.name.setText(mTimelineItems.get(position).name);

    // place and goingTo of the TimelineItem

    // if combinedPlace == ""
    if(mTimelineItems.get(position).combinedPlace.equals("")) {
        if (mTimelineItems.get(position).isFirstDayOfEvent) {
            holder.place.setText(mTimelineItems.get(position).place);
        } else {
            holder.place.setText("");
        }
        if (mTimelineItems.get(position).isLastDayOfEvent) {
            holder.goingTo.setText(mTimelineItems.get(position).goingTo);
        } else {
            holder.goingTo.setText("");
        }
        holder.combinedPlace.setText("");
    } else {
        holder.place.setText("");
        holder.goingTo.setText("");
        holder.combinedPlace.setText(mTimelineItems.get(position).combinedPlace);
    }

    if(mTimelineItems.get(position).startDateTime != null) {
        holder.startTime.setText(mTimelineItems.get(position).startDateTime.toString("HH:mm"));
    } else {
        holder.startTime.setText("");
    }

    if(mTimelineItems.get(position).endDateTime != null) {
        holder.endTime.setText(mTimelineItems.get(position).endDateTime.toString("HH:mm"));
    } else {
        holder.endTime.setText("");
    }


    if (!mTimelineItems.get(position).isShowPreviousMonthsItem) {
        if (mTimelineItems.get(position).date.getDayOfWeek() == DateTimeConstants.SUNDAY) {
            holder.dayOfWeek.setTextColor(Color.RED);
            holder.dayOfMonth.setTextColor(Color.RED);
        } else {
            holder.dayOfWeek.setTypeface(null, Typeface.NORMAL);
            holder.dayOfMonth.setTypeface(null, Typeface.NORMAL);
            holder.dayOfWeek.setTextColor(Color.GRAY);
            holder.dayOfMonth.setTextColor(Color.GRAY);
        }
    } else {
        ((RelativeLayout) holder.dayOfWeek.getParent()).setBackgroundColor(Color.WHITE);
    }

    holder.bindTimelineItem(mTimelineItems.get(position));
}

// Return the size of the data set (invoked by the layout manager)
@Override
public int getItemCount() {
    return mTimelineItems.size();
}

// replace the data set
public void setTimelineItems(ArrayList<TimelineItem> timelineItems) {
    this.mTimelineItems = timelineItems;
}

// replace an item in the data set
public void swapTimelineItemAtPosition(TimelineItem item, int position) {
    mTimelineItems.remove(position);
    mTimelineItems.add(position, item);
    notifyItemChanged(position);
}

// the ViewHolder class containing the relevant views,
// also binds the Timeline item itself to handle onClick events
public class TimelineItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    protected TextView dayOfWeek;
    protected TextView dayOfMonth;
    protected TextView showPreviousMonthsText;
    protected TextView name;
    protected TextView place;
    protected TextView combinedPlace;
    protected TextView goingTo;
    protected TextView startTime;
    protected TextView endTime;

    protected TimelineItem timelineItem;

    public TimelineItemViewHolder(View view) {
        super(view);
        view.setOnClickListener(this);
        this.dayOfWeek = (TextView) view.findViewById(R.id.day_of_week);
        this.dayOfMonth = (TextView) view.findViewById(R.id.day_of_month);
        this.showPreviousMonthsText = (TextView) view.findViewById(R.id.load_previous_data);
        this.name = (TextView) view.findViewById(R.id.name);
        this.place = (TextView) view.findViewById(R.id.place);
        this.combinedPlace = (TextView) view.findViewById(R.id.combined_place);
        this.goingTo = (TextView) view.findViewById(R.id.going_to);
        this.startTime = (TextView) view.findViewById(R.id.start_time);
        this.endTime = (TextView) view.findViewById(R.id.end_time);
    }

    public void bindTimelineItem(TimelineItem item) {
        timelineItem = item;
    }

    // handles the onClick of a TimelineItem
    @Override
    public void onClick(View v) {
        // if the TimelineItem is a ShowPreviousMonthsItem
        if (timelineItem.isShowPreviousMonthsItem) {
            BusProvider.getInstance().post(new ShowPreviousMonthsRequest());
        }
        // if the TimelineItem is a PlaceholderItem
        else if (timelineItem.isPlaceholderItem) {
            Toast.makeText(v.getContext(), "(no details)", Toast.LENGTH_SHORT).show();
        }
        // else the TimelineItem is an actual event
        else {
            Toast.makeText(v.getContext(), "eventId = " + timelineItem.eventId, Toast.LENGTH_SHORT).show();
        }
    }
}

这是在事件总线上发布更改时在TimelineFragment中触发的方法:

@Subscribe
public void onTimelineItemChanged(TimelineItemChangedNotification notification) {
    int position = notification.position;
    Log.d(TAG, "TimelineItemChanged detected for position " + position);
    mAdapter.swapTimelineItemAtPosition(mTimeline.mTimelineItems.get(position), position);
    mAdapter.notifyItemChanged(position);
    Log.d(TAG, "Item for position " + position + " swapped");
}

需要注意的是,在我将更改的数据滚动到足够远并返回到该位置之后,适配器的数据集似乎可以正确显示。 最初,UI完全混乱了。

编辑:

我发现

mAdapter.notifyItemRangeChanged(position, mAdapter.getItemCount());

解决了这个问题,但是-不幸的是-将滚动位置设置为要更改的位置:(

这是我的时间线片段:

/**
* Fragment displaying a Timeline using a RecyclerView
*/
public class TimelineFragment extends BackHandledFragment {
    // DEBUG flag and TAG
    private static final boolean DEBUG = false;
    private static final String TAG = TimelineFragment.class.getSimpleName();

// variables
protected RecyclerView mRecyclerView;
protected TimelineAdapter mAdapter;
protected LinearLayoutManager mLinearLayoutManager;
protected Timeline mTimeline;
protected MenuItem mMenuItemScroll2Today;
protected MenuItem mMenuItemReload;
protected String mToolbarTitle;
// TODO: get the value of this boolean from the shared preferences
private boolean mUseTimelineItemDividers = true;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // get a handle to the app's Timeline singleton
    mTimeline = Timeline.getInstance();
    setHasOptionsMenu(true);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    View rootView = inflater.inflate(R.layout.fragment_timeline, container, false);
    rootView.setTag(TAG);

    mRecyclerView = (RecyclerView) rootView.findViewById(R.id.timeline_list);
    mRecyclerView.hasFixedSize();

    // LinearLayoutManager constructor
    mLinearLayoutManager = new LinearLayoutManager(getActivity());
    // set the layout manager
    setRecyclerViewLayoutManager();
    // adapter constructor
    mAdapter = new TimelineAdapter(mTimeline.mTimelineItems);
    // set the adapter for the RecyclerView.
    mRecyclerView.setAdapter(mAdapter);

    // add lines between the different items if using them
    if (mUseTimelineItemDividers) {
        RecyclerView.ItemDecoration itemDecoration =
                new TimelineItemDivider(this.getContext());
        mRecyclerView.addItemDecoration(itemDecoration);
    }

    // add the onScrollListener
    mRecyclerView.addOnScrollListener(new TimelineOnScrollListener(mLinearLayoutManager) {
        // when the first visible item on the Timeline changes,
        // adjust the Toolbar title accordingly
        @Override
        public void onFirstVisibleItemChanged(int position) {
            mTimeline.mCurrentScrollPosition = position;
            try {
                String title = mTimeline.mTimelineItems
                        .get(position).date
                        .toString(TimelineConfig.TOOLBAR_DATE_FORMAT);
                // if mToolbarTitle is null, set it to the new title and post on bus
                if (mToolbarTitle == null) {
                    if (DEBUG)
                        Log.d(TAG, "mToolbarTitle is null - posting new title request on bus: " + title);
                    mToolbarTitle = title;
                    BusProvider.getInstance().post(new ChangeToolbarTitleRequest(mToolbarTitle));
                } else { // if mToolbarTitle is not null
                    // only post on the bus if the new title is different from the previous one
                    if (!title.equals(mToolbarTitle)) {
                        if (DEBUG)
                            Log.d(TAG, "mToolbarTitle is NOT null, but new title detected - posting new title request on bus: " + title);
                        mToolbarTitle = title;
                        BusProvider.getInstance().post(new ChangeToolbarTitleRequest(mToolbarTitle));
                    }
                }

            } catch (NullPointerException e) {
                // if the onFirstVisibleItemChanged is called on a "ShowPreviousMonthsItem",
                // leave the title as it is
            }
        }
    });

    return rootView;
}

/**
 * Set RecyclerView's LayoutManager to the one given.
 */
public void setRecyclerViewLayoutManager() {
    int scrollPosition;

    // If a layout manager has already been set, get current scroll position.
    if (mRecyclerView.getLayoutManager() != null) {
        scrollPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager())
                .findFirstCompletelyVisibleItemPosition();
    } else {
        scrollPosition = mTimeline.mFirstPositionForToday;
    }

    mRecyclerView.setLayoutManager(mLinearLayoutManager);
    mLinearLayoutManager.scrollToPositionWithOffset(scrollPosition, 0);
}

// set additional menu items for the Timeline fragment
@Override
public void onPrepareOptionsMenu(Menu menu) {
    // scroll to today
    mMenuItemScroll2Today = menu.findItem(R.id.action_scroll2today);
    mMenuItemScroll2Today.setVisible(true);
    mMenuItemScroll2Today.setIcon(Timeline.getIconForDateTime(new DateTime()));
    mMenuItemScroll2Today.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
        @Override
        public boolean onMenuItemClick(MenuItem item) {
            // stop scrolling
            mRecyclerView.stopScroll();
            // get today's position
            int todaysPosition = mTimeline.mFirstPositionForToday;
            // scroll to today's position
            mLinearLayoutManager.scrollToPositionWithOffset(todaysPosition, 0);
            return false;
        }
    });

    // reload data from Hacklberry
    mMenuItemReload = menu.findItem(R.id.action_reload_from_hacklberry);
    mMenuItemReload.setVisible(true);
    mMenuItemReload.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
        @Override
        public boolean onMenuItemClick(MenuItem item) {
            // stop scrolling
            mRecyclerView.stopScroll();
            //
            mTimeline.reloadDBForCurrentMonth();
            mTimeline.loadEventsFromUninfinityDBAsync(mTimeline.mTimelineStart, mTimeline.mTimelineEnd);
            return false;
        }
    });

    super.onPrepareOptionsMenu(menu);
}

@Override
public void onResume() {
    super.onResume();
    // if the Timeline has been invalidated, let AllInOneActivity know it needs to replace
    // this Fragment with a new one
    if (mTimeline.isInvalidated()) {
        Log.d(TAG, "posting TimelineInvalidatedNotification on the bus ...");
        BusProvider.getInstance().post(
                new TimelineInvalidatedNotification());
    }
    // fetch today's menu icon
    if (mMenuItemScroll2Today != null) {
        if (DEBUG) Log.d(TAG, "fetching scroll2today menu icon");
        mMenuItemScroll2Today.setIcon(Timeline.getIconForDateTime(new DateTime()));
    }
}

// from BackHandledFragment
@Override
public String getTagText() {
    return TAG;
}

// from BackHandledFragment
@Override
public boolean onBackPressed() {
    return false;
}

@Subscribe
public void onHacklberryReloaded(HacklberryLoadedNotification notification) {
    resetReloading();
}

// handles ShowPreviousMonthsRequests posted on the bus by the TimelineAdapter's ShowPreviousMonthsItem onClick()
@Subscribe
public void onShowPreviousMonthsRequest(ShowPreviousMonthsRequest request) {
    // create an empty OnItemTouchListener to prevent the user from manipulating
    // the RecyclerView while it loads more data (would mess up the scroll position)
    EmptyOnItemTouchListener listener = new EmptyOnItemTouchListener();
    // add it to the RecyclerView
    mRecyclerView.addOnItemTouchListener(listener);
    // load the previous months (= add the required TimelineItems)
    int newScrollToPosition = mTimeline.showPreviousMonths();
    // pass the new data set to the TimelineAdapter
    mAdapter.setTimelineItems(mTimeline.mTimelineItems);
    // notify the adapter the data set has changed
    mAdapter.notifyDataSetChanged();
    // scroll to the last scroll (updated) position
    mLinearLayoutManager.scrollToPositionWithOffset(newScrollToPosition, 0);
}

@Subscribe
public void onTimelineItemChanged(TimelineItemChangeNotification notification) {
    int position = notification.position;
    Log.d(TAG, "TimelineItemChanged detected for position " + position);
    mAdapter.swapTimelineItemAtPosition(mTimeline.mTimelineItems.get(position), position);
    //mAdapter.notifyItemRangeChanged(position, position);
    Log.d(TAG, "Item for position " + position + " swapped");
}

首次加载应用程序后,我已为其截屏。 我将快速解释初始化过程:

  1. 时间轴是通过用PlaceholderItems(只有日期的TimelineItem)填充整天来构建的。
  2. 事件从数据库加载并转换为TimelineItems
  3. 每当新的TimelineItem更改并准备就绪时,时间线就会通过otto总线戳入TimelineFragment,以使用新的TimelineItem为该特定位置更新适配器的数据集。

这是初始加载后发生的屏幕截图:

时间轴已加载,但某些项目插入了错误的位置。

在此处输入图片说明

当滚动并返回到之前显示不正确的天数范围时,一切都很好:

在此处输入图片说明

在此处输入图片说明

关于第二种方法。 可能您的代码不是workind,因为您在mTimelineItemsmCachedEvents上存在数据争用。 我不能看到所有的代码,但似乎你使用mTimelineItems内部doInBackground()同时没有任何同步UI线程。

我建议您将第一种和第二种方法混合使用:

  1. 复制原始数据( mTimelineItems )并将其发送到AsyncTask
  2. doInBackground()异步更改副本,并记录所有更改。
  3. 返回更改的数据并记录到UI线程。
  4. 通过使用日志将新数据应用于RecyclerView。

让我用代码说明这种方法。

数据管理:

public class AsyncDataUpdater
{
    /**
     * Example data entity. We will use it
     * in our RecyclerView.
     */
    public static class TimelineItem
    {
        public final String name;
        public final float value;

        public TimelineItem(String name, float value)
        {
            this.name = name;
            this.value = value;
        }
    }

    /**
     * That's how we will apply our data changes
     * on the RecyclerView.
     */
    public static class Diff
    {
        // 0 - ADD; 1 - CHANGE; 2 - REMOVE;
        final int command;
        final int position;

        Diff(int command, int position)
        {
            this.command = command;
            this.position = position;
        }
    }

    /**
     * And that's how we will notify the RecyclerView
     * about changes.
     */
    public interface DataChangeListener
    {
        void onDataChanged(ArrayList<Diff> diffs);
    }


    private static class TaskResult
    {
        final ArrayList<Diff> diffs;
        final ArrayList<TimelineItem> items;

        TaskResult(ArrayList<TimelineItem> items, ArrayList<Diff> diffs)
        {
            this.diffs = diffs;
            this.items = items;
        }
    }

    private class InsertEventsTask extends AsyncTask<Void, Void, TaskResult>
    {
        //NOTE: this is copy of the original data.
        private ArrayList<TimelineItem> _old_items;

        InsertEventsTask(ArrayList<TimelineItem> items)
        {
            _old_items = items;
        }

        @Override
        protected TaskResult doInBackground(Void... params)
        {
            ArrayList<Diff> diffs = new ArrayList<>();

            try
            {
                //TODO: long operation(Database, network, ...).
                Thread.sleep(1000);
            }
            catch(InterruptedException e)
            {
                e.printStackTrace();
            }

            //Some crazy manipulation with data...
            //NOTE: we change the copy of the original data!
            Random rand = new Random();
            for(int i = 0; i < 10; i ++)
            {
                float rnd = rand.nextFloat() * 100.0f;
                for(int j = 0; j < _old_items.size(); j++)
                {
                    if(_old_items.get(j).value > rnd)
                    {
                        TimelineItem item = new TimelineItem("Item " + rnd, rnd);
                        //Change data.
                        _old_items.add(j, item);
                        //Log the changes.
                        diffs.add(new Diff(0, j));
                        break;
                    }
                }
            }

            for(int i = 0; i < 5; i ++)
            {
                int rnd_index = rand.nextInt(_old_items.size());
                //Change data.
                _old_items.remove(rnd_index);
                //Log the changes.
                diffs.add(new Diff(2, rnd_index));
            }
            //...

            return new TaskResult(_old_items, diffs);
        }

        @Override
        protected void onPostExecute(TaskResult result)
        {
            super.onPostExecute(result);

            //Apply the new data in the UI thread.
            _items = result.items;
            if(_listener != null)
                _listener.onDataChanged(result.diffs);
        }
    }

    private DataChangeListener _listener;
    private InsertEventsTask _task = null;

    /** Managed data. */
    private ArrayList<TimelineItem> _items = new ArrayList<>();

    public AsyncDataUpdater()
    {
        // Some test data.
        for(float i = 10.0f; i <= 100.0f; i += 10.0f)
            _items.add(new TimelineItem("Item " + i, i));
    }

    public void setDataChangeListener(DataChangeListener listener)
    {
        _listener = listener;
    }

    public void updateDataAsync()
    {
        if(_task != null)
            _task.cancel(true);

        // NOTE: we should to make the new copy of the _items array.
        _task = new InsertEventsTask(new ArrayList<>(_items));
        _task.execute();
    }

    public int getItemsCount()
    {
        return _items.size();
    }

    public TimelineItem getItem(int index)
    {
        return _items.get(index);
    }
}

在用户界面中使用:

public class MainActivity extends AppCompatActivity
{
    private static class ViewHolder extends RecyclerView.ViewHolder
    {
        private final TextView name;
        private final ProgressBar value;

        ViewHolder(View itemView)
        {
            super(itemView);

            name = (TextView)itemView.findViewById(R.id.tv_name);
            value = (ProgressBar)itemView.findViewById(R.id.pb_value);
        }

        void bind(AsyncDataUpdater.TimelineItem item)
        {
            name.setText(item.name);
            value.setProgress((int)item.value);
        }
    }

    private static class Adapter extends RecyclerView.Adapter<ViewHolder>
                    implements AsyncDataUpdater.DataChangeListener
    {
        private final AsyncDataUpdater _data;

        Adapter(AsyncDataUpdater data)
        {
            _data = data;
            _data.setDataChangeListener(this);
        }

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

        @Override
        public void onBindViewHolder(ViewHolder holder, int position)
        {
            holder.bind(_data.getItem(position));
        }

        @Override
        public int getItemCount()
        {
            return _data.getItemsCount();
        }

        @Override
        public void onDataChanged(ArrayList<AsyncDataUpdater.Diff> diffs)
        {
            //Apply changes.
            for(AsyncDataUpdater.Diff d : diffs)
            {
                if(d.command == 0)
                    notifyItemInserted(d.position);
                else if(d.command == 1)
                    notifyItemChanged(d.position);
                else if(d.command == 2)
                    notifyItemRemoved(d.position);
            }
        }
    }

    private AsyncDataUpdater _data = new AsyncDataUpdater();

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView rv_content = (RecyclerView)findViewById(R.id.rv_content);
        rv_content.setLayoutManager(new LinearLayoutManager(this));
        rv_content.setAdapter(new Adapter(_data));

        Button btn_add = (Button)findViewById(R.id.btn_add);
        btn_add.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                _data.updateDataAsync();
            }
        });
    }
}

我将Example应用程序放在GH上,因此您可以根据需要对其进行测试。

更新1

关于数据竞赛。

  1. this.mTimelineItems = timelineItems; TimelineAdapter()构造函数内部,将复制对ArrayList的引用,但不复制ArrayList本身。 因此,您有两个引用: TimelineAdapter.mTimelineItemsTimeline.mTimelineItems ,它们都引用相同的ArrayList对象。 请看看这个

  2. doInBackground()Worker Thread调用doInBackground()和从UI Thread调用onProgressUpdate()时发生数据争用。 主要原因是publishProgress()同步调用onProgressUpdate() 相反, publishProgress() 计划将来在UI线程调用 onProgressUpdate() 是一个很好的问题描述。

题外话。

这个:

mTimelineItems.set(position, item);

应该比这更快:

mTimelineItems.remove(position);
mTimelineItems.add(position, item);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM