photo player 显示☞列表选中项的处理
一、需求
- 需要显示一个图片列表,可以左右滑动,点击则在大图显示该图;
- 可以在界面上显示大图,可以左右滑动切换图片,可以支持zoom、move、rotate;
- 大图与图片列表相互关联:
- 点击列表item可以在show对应大图;
- 大图切换则列表保持该Item在列表中间位置;
- 对于大图显示的图片添加红框标记(item的两个背景);
效果如此图:
二、当前方案
- 大图显示使用ViewPager控件实现,则可以支持左右滑动切换图片功能(支持预加载);
- 图片列表使用RecyclerView控件来实现,则可以支持列表功能,同时其缓存机制对于机器更加友好;
- 列表关联功能可以自行实现:
- 点击item show对应大图:对每个item添加点击事件监听,点击则设置viewpager显示指定position图片;
- 添加对于ViewPager的滑动监听,添加对于列表移动的处理;
- 显示的图片在列表中设置红框标记,这里前后使用两个方案实现:
- 使用arraylist维护对应的标记,在show图片时添加判断,viewpager切换则刷新列表显示;
- 在adapter中维护一个变量,记录当前显示的图片,viewpager切换则刷新列表中背景切换的两个item,且仅处理其背景,并不刷新该item中imageview;
本次记录的重点在于红框标记的两个方案实现,方案2比方案1节省了大量资源:列表、刷新item数量、控件
三、关联具体实现
3.1 方案一:
-
adapter中维护一个boolean类型的list,大小与imagelist相同,选中设置为true,其余设置为false:
public List<Boolean> isClicks; //控件是否被点击,默认为false,如果被点击,改变值,控件根据值改变自身颜色 isClicks = new ArrayList<>(); for (int i = 0; i < mImageList.size(); i++) { isClicks.add(false); }
-
在init list item view的时候,判断上述值:
@Override public void onBindViewHolder(final ViewHolder holder, int position) { ... if (isClicks.get(position)) { imageItem.setBackgroundResource(R.drawable.photo_preview__btn_p); } else { imageItem.setBackgroundResource(R.drawable.photo_preview__btn_n); } ... }
-
ViewPager滑动的时候改变对应的值,并更新:
private NoPreloadViewPager.OnPageChangeListener mPhotoPageChangeListener = new NoPreloadViewPager.OnPageChangeListener() { @Override public void onPageSelected(int position) { mPosition = position; mRecycleAdapter.isClicks.set(mPosition, true); for(int i = 0; i <mRecycleAdapter.isClicks.size();i++){ mRecycleAdapter.isClicks.set(i,false); } mRecycleAdapter.isClicks.set(position,true); //此接口会将所有可见的item重新刷新一遍 mRecycleAdapter.notifyDataSetChanged(); MoveToPosition(mLayoutManager, mPhotoListView, position); } @Override public void onPageScrolled(int arg0, float arg1, int arg2) { } @Override public void onPageScrollStateChanged(int arg0) { //arg0 ==1的时表示正在滑动,arg0==2的时表示滑动完毕了,arg0==0的时表示什么都没做。 if (arg0 == 0) { } else if (arg0 == 1) { } else if (arg0 == 2) { } } }; //根据当前view切换将要显示的item移动到中间位置 public void MoveToPosition(LinearLayoutManager manager, RecyclerView recyclerView, int position) { int firstItem = manager.findFirstVisibleItemPosition(); int lastItem = manager.findLastVisibleItemPosition(); int offset = (lastItem - firstItem) / 2; int maxItem = mPhotoList.size() - 1; int center = (lastItem + firstItem) / 2; int offsetPosition = 0; if (position < center) { offsetPosition = position - offset; } else if (position == center) { offsetPosition = position; } else { offsetPosition = position + offset; } if (offsetPosition < 0) { offsetPosition = 0; } else if (offsetPosition > maxItem) { offsetPosition = maxItem; } recyclerView.scrollToPosition(offsetPosition); }
-
列表item点击时:
private PhotoListAdapter.Callback mPhotoListItemCallback = new PhotoListAdapter.Callback() { @Override public void onItemClick(Image image, ImageView imageItem) { mPosition = mPhotoList.indexOf(image); //todo set the current item in center mPhotoView.setCurrentItem(mPosition); } };
这里在点击列表item的时候并没有做notify item列表的处理:1. 这里两个控件要避免操作成环;
2. ViewPager控件中的setCurrentItem接口,在其内部实现中会调用到:mOnPageChangeListener.onPageSelected(item);/** * Set the currently selected page. If the ViewPager has already been through its first * layout there will be a smooth animated transition between the current item and the * specified item. * @param item Item index to select */ public void setCurrentItem(int item) { mPopulatePending = false; setCurrentItemInternal(item, !mFirstLayout, false); } /** * Set the currently selected page. * @param item Item index to select * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately */ public void setCurrentItem(int item, boolean smoothScroll) { mPopulatePending = false; setCurrentItemInternal(item, smoothScroll, false); } void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) { setCurrentItemInternal(item, smoothScroll, always, 0); } void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { if (mAdapter == null || mAdapter.getCount() <= 0) { setScrollingCacheEnabled(false); return; } if (!always && mCurItem == item && mItems.size() != 0) { setScrollingCacheEnabled(false); return; } if (item < 0) { item = 0; } else if (item >= mAdapter.getCount()) { item = mAdapter.getCount() - 1; } final int pageLimit = mOffscreenPageLimit; if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) { // We are doing a jump by more than one page. To avoid // glitches, we want to keep all current pages in the view // until the scroll ends. for (int i=0; i<mItems.size(); i++) { mItems.get(i).scrolling = true; } } final boolean dispatchSelected = mCurItem != item; mCurItem = item; populate(); final int destX = (getWidth() + mPageMargin) * item; if (smoothScroll) { smoothScrollTo(destX, 0, velocity); if (dispatchSelected && mOnPageChangeListener != null) { mOnPageChangeListener.onPageSelected(item); } } else { if (dispatchSelected && mOnPageChangeListener != null) { mOnPageChangeListener.onPageSelected(item); } completeScroll(); scrollTo(destX, 0); } }
所以这里按照上述方式实现:
- 维护一个list;
- 每次更新都在重新设置一遍list;
- notify 数据变化时,当前可见的item都要刷新一遍;
3.2 方案二:
基于上述方案进行优化:
- 由于每次选中的图片仅一个,则可以使用一个变量来维护;
- 每次在view切换的时候,现在是更新整个list,而这里可以仅设置一个值;
- 每次在view切换之后需要通知列表更新数据,现在是更新所有可见列表,这里可以仅更新两个item;
- 每次更新view的时候,都是将这个view完整的重新加载,这里可以仅更新item的背景图,而不去重新加载图片;
则具体来看code实现:
-
使用变量来标记当前显示的图片:
//M: add select solution private int selectPosition = -1; public void setSelectPosition(int position) { if (0 > position ||mImageList.size() < position ){ Debug.i(TAG, "position error: " + position); return; } selectPosition = position; } public int getSelectPosition( ) { return selectPosition; }
-
获取此前一次显示的位置,更新当前显示的位置,并通知adapter有数据变化::
//add viewpager item click listener private NoPreloadViewPager.OnPageChangeListener mPhotoPageChangeListener = new NoPreloadViewPager.OnPageChangeListener() { int oldPosition = 0; @Override public void onPageSelected(int position) { mPosition = position; //获取前一个显示的position,并设置当前显示的position; oldPosition = mRecycleAdapter.getSelectPosition(); mRecycleAdapter.setSelectPosition(position); //注意,这里更新使用notifyItemChanged,仅更新上述两个位置的item //第二个参数为0表示payloads不为空,是只更新item某一个控件,而不是完整更新的关键 mRecycleAdapter.notifyItemChanged(oldPosition, 0); mRecycleAdapter.notifyItemChanged(position, 0); //照例让RecyclerView滑动到指定位置 MoveToPosition(mLayoutManager, mPhotoListView, position); } @Override public void onPageScrolled(int position, float arg1, int arg2) { } @Override public void onPageScrollStateChanged(int arg0) { //arg0 ==1的时表示正在滑动,arg0==2的时表示滑动完毕了,arg0==0的时表示什么都没做。 if (arg0 == 0) { } else if (arg0 == 1) { } else if (arg0 == 2) { } } };
-
adapter中处理仅更新item的背景图:
@Override public void onBindViewHolder(final ViewHolder holder, int position) { }//此函数不去实现,转而实现下述带payloads参数的接口 @Override public void onBindViewHolder(final ViewHolder holder, final int position, List payloads) { final Image image = mImageList.get(position); final ImageView imageItem = holder.imageItem; final ProgressBar loading = holder.loading; final String imagePath = image.getImagePath(); if(payloads.isEmpty()) {//这里是关键:正常刷新时走这里,主要是load图片和添加click监听 imageItem.setImageDrawable(mContext.getDrawable(R.drawable.photo_preview__btn_n)); loading.setVisibility(View.VISIBLE); holder.imageItem.setTag(imagePath); mImageLoader.loadImage(holder.imageItem, imagePath, true, position, new ImageLoader.onImageLoadedListener() { @Override public void displayImage(ImageView view, Bitmap bitmap) { //check if there is the correct view String path = view.getTag().toString(); if (path.equals(imagePath)) { view.setImageBitmap(bitmap); loading.setVisibility(View.GONE); } } }); if (mCallback != null) { holder.imageItem.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mCallback.onItemClick(position, imageItem); } }); } } else {//选中图片背景图更新时走这里,只判断position更新背景图 //set background resource //init or select all need set the background rescource, so move it // to this function out } if (position == selectPosition) { imageItem.setBackgroundResource(R.drawable.photo_preview__btn_p); } else { imageItem.setBackgroundResource(R.drawable.photo_preview__btn_n); } }
整体来讲改动比较小,但是加载图片时对于资源的消耗要降低了很多