Android realizes a silky automatic carousel control example code

  • 2021-10-11 19:37:58
  • OfStack

Preface

At present, many App have banner interface with automatic carousel, which is used to display advertisement pictures or display some popular activities at present. Besides cool effect, it is also a great design point to reduce the occupation of interface by carousel. This paper mainly summarizes the realization process of automatic carousel control and some optimization skills for this kind of control.

1. How to achieve

Before we start programming our code, We have to think about it first. In the official Api provided by Google, are there any similar controls that have achieved similar functions? After all, most of the official controls have passed the test of time, and they are very good in terms of stability and performance. If we can carry out corresponding transformation based on the official controls, the stability of the controls will be relatively guaranteed.

Among the more common mainstream controls, ViewPager and RecyclerView have already realized similar functions, especially ViewPager, which can be said to have realized most of the functions of our control, so if we transform based on ViewPager, we can also make our carousel control more stable.

There are two main differences between ViewPager and the automatic carousel control we need:

Autoplay is not supported Unable to slide from the last sheet to the first sheet

So we mainly aim at these two parts of the corresponding transformation, so as to achieve our own automatic carousel control.

1.1 Realize automatic carousel function

To realize the automatic carousel function, Our first thought should be to realize the timer function through Timer or ScheduledExecutorService. Then let ViewPager set the current Item to the data of the next position through serCurrentItem (int position) method. However, if it is realized through timer, there will be a problem, that is, it is troublesome when we need banner to stop playing, so it is reasonable to send events in the form of sendMessage through Handler to realize automatic carousel of ViewPager and stop some scenes.

Let's look at the code:


 private static class AutoScrollHandler extends Handler {

 private WeakReference<AutoScrollViewPager> mBannerRef;

 private static final int MSG_CHANGE_SELECTION = 1;

 AutoScrollHandler(AutoScrollViewPager autoScrollViewPager) {
  mBannerRef = new WeakReference<>(autoScrollViewPager);
 }

 private void start() {
  removeMessages(MSG_CHANGE_SELECTION);
  sendEmptyMessageDelayed(MSG_CHANGE_SELECTION, AUTO_SCROLL_TIME);
 }

 private void stop() {
  removeMessages(MSG_CHANGE_SELECTION);
 }

 @Override
 public void handleMessage(Message msg) {
  if (msg.what == MSG_CHANGE_SELECTION) {
  if (mBannerRef == null || mBannerRef.get() == null) {
   return;
  }
  AutoScrollViewPager banner = mBannerRef.get();

  if (banner.mSelectedIndex == Integer.MAX_VALUE) {
   int rightPos = banner.mSelectedIndex % banner.mBannerList.size();
   banner.setCurrentItem(banner.getInitPosition() + rightPos + 1, true);
  } else {
   if (!hasMessages(MSG_CHANGE_SELECTION)) {
   banner.setCurrentItem(banner.mSelectedIndex + 1, true);
   sendEmptyMessageDelayed(MSG_CHANGE_SELECTION, AUTO_SCROLL_TIME);
   }
  }

  }
 }
 }

It can be seen that we first pass in the external ViewPager, and then prevent memory leakage in the form of weak reference. By calling the setCurrentItem () method in the handlerMessage () method, the Item of the current ViewPager is set to the corresponding position + 1 data, so we only need to call the sendMessage () method of Handler externally to make ViewPager automatically carry out unlimited carousel.

1.2 Slide ViewPager from the last sheet to the first sheet

We know that ViewPager can't slide from the last page to the first page, but we can change our thinking. If we set the size of ViewPager to infinity through getCount () method in Adapter of ViewPager, and then ensure that the sliding page 1 directly corresponds to the data of the data source by taking the remainder, so that ViewPager can achieve the effect of sliding from the last one to the first one.


 public int getCount() {
  if (mBannerList == null) {
   return 0;
  }
  if (mBannerList.size() == 1) {
   return 1;
  } else {
   return Integer.MAX_VALUE;
  }
 }

 public Object instantiateItem(ViewGroup container, final int position) {
  if (mBannerList != null && mBannerList.size() > 0) {
   View imageView = null;
   Uri uri = Uri.parse(mBannerList.get(position % mBannerList.size()).cover_url); //  By taking the remainder, 
   imageView = new SimpleDraweeView(mContext);
   ((SimpleDraweeView) imageView).setImageURI(uri);
   container.addView(imageView);
   return imageView;
  }
  return null;
 }

2. How to optimize

Above, we simply realized the automatic carousel function of ViewPager. But in fact, there are still many details that we need to optimize. For example, we set the size of ViewPager to infinity. To realize sliding from the last one to the first one, but if there is no cache at this time, we need to return a lot of View from the new new in the instantiateItem (ViewGroup container, final int position) method of Adapter, which will cause unnecessary memory waste. Only by optimizing these details can our controls be more easy to use, stable and performance.

2.1 Reducing memory waste through caching

In order to enable ViewPager to realize the function of wireless carousel, we use the method of setting the size of getCount () to infinity, but this will cause a problem, which will make us return many View from the new new in the instantiateItem () method of Adapter, resulting in unnecessary memory waste.

Therefore, we can use an List as the cache pool, and store the discarded object in the destroyItem () method in Adapter into the cache pool for reuse, so as to avoid memory waste.


 private final ArrayList<View> mViewCaches = new ArrayList<>();

 @Override
 public void destroyItem(ViewGroup container, int position, Object object) {
  ImageView imageView = (ImageView) object;
  container.removeView(imageView);
  mViewCaches.add(imageView);
 }

Then, in the instantiateItem () method of Adapter, the cached View is taken out from List and reused


 public Object instantiateItem(ViewGroup container, final int position) {
  if (mBannerList != null && mBannerList.size() > 0) {
   View imageView = null;
   Uri uri = Uri.parse(mBannerList.get(position % mBannerList.size()).cover_url);
   if (mViewCaches.isEmpty()) {
    imageView = new SimpleDraweeView(GlobalContext.getContext());
   } else {
    //  When there is data in the cache set, reuse it 
    imageView = (ImageView) mViewCaches.remove(0);
   }
 }

2.2 Appropriate stop of automatic carousel

When we touch Banner or leave the current page showing Banner, if banner is still in wireless carousel, it will cause unnecessary performance loss, so we need to stop the carousel of Banner when touching Banner and the current Activity is invisible, so as to improve performance.


 public boolean dispatchTouchEvent(MotionEvent ev) {
  int action = ev.getAction();
  if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL
    || action == MotionEvent.ACTION_OUTSIDE) {
   startAutoPlay();
  } else if (action == MotionEvent.ACTION_DOWN) {
   stopAutoPlay();
  }
  return super.dispatchTouchEvent(ev);
 }

2.3 Change ViewPager switching speed

When the native ViewPager is automatically carousel, the switching speed is very fast, which will give people a very abrupt feeling. Moreover, ViewPager does not provide an interface for us to set the switching speed of ViewPager, so we need to use Scroller to set the switching speed through reflection, so as to make our Banner more silky.


 public AutoScrollViewPager(Context context) {
  this(context, null);
  initViewPagerScroll();
 }

 private void initViewPagerScroll() {
  try {
   Field mField = ViewPager.class.getDeclaredField("mScroller");
   mField.setAccessible(true);
   BannerScroller scroller = new BannerScroller(getContext());
   mField.set(this, scroller);
  } catch (Exception e) {
   Log.d(TAG, e.getMessage());
  }
 }

public class BannerScroller extends Scroller {

 private static final int BANNER_DURATION = 1000;
 private int mDuration = BANNER_DURATION;

 public BannerScroller(Context context) {
  super(context);
 }

 @Override
 public void startScroll(int startX, int startY, int dx, int dy, int duration) {
  super.startScroll(startX, startY, dx, dy, mDuration);
 }
}

At this point, our automatic carousel control, both in performance and stability have been very good.

Summarize


Related articles: