Androidは滑らかな自動マルチキャストコントロールを作ります

7173 ワード

現在、多くのAppには自動マルチキャストのbannerインタフェースがあり、広告画像を展示したり、現在人気のある活動を表示したりするために使用されており、比較的クールな効果を備えているほか、マルチキャストでインタフェースの占有を減らすことも素晴らしい設計点です.本文は主に自動マルチキャストコントロールの実現過程と,このようなコントロールのいくつかの最適化の技術を総括する.
一、どのように実現するか
私たちのコードプログラミングを始める前に、Googleが提供している公式Apiの中で、似たようなコントロールが似たような機能を実現しているかどうかを考えてみましょう.結局、公式のコントロールの多くは時間の試練を経て、安定性も性能の面でも非常に良いので、公式のコントロールに基づいて相応の改造を行うことができれば、コントロールの安定性も相対的に保障されます.
比較的一般的な主流のコントロールの中で、実はViewPagerとRecyclerViewはすでに類似の機能を実現して、特にViewPagerは、すでに私たちのこのコントロールの大部分の機能を実現したと言えるので、私たちがViewPagerに基づいて改造すれば、私たちのマルチキャストコントロールをもっと安定させることができます.
では、ViewPagerは私たちが必要とする自動マルチキャストコントロールとどのくらいの差がありますか.主に2つあります.
  • 自動再生
  • はサポートされていません.
  • 最後の1枚から1枚目の
  • にスライドできません.
    だから私たちは主にこの2つの部分に対して相応の改造を行い、それによって私たち自身の自動放送コントロールを実現します.
    1.1自動ローテーション機能の実現
    オートローテーション機能を実現するには、TimerまたはS h e d u l edExecutorServiceでタイマーの機能を実現し、ViewPagerにserCurrentItem(int position)メソッドで現在のItemを次のpositionのデータに設定させるべきだが、タイマーで実現すると問題があり、それは私たちがbannerに再生を停止させる必要があるときに面倒なので、HandlerがsendMessageという形で、イベントの送信を行うことでViewPagerの自動ローテーションを実現したり、一部のシーンの停止を実現したりするのが合理的です.
    コードを見てみましょう.
        private static class AutoScrollHandler extends Handler {
    
            private WeakReference 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);
                        }
                    }
    
                }
            }
        }
    

    外部のViewPagerに転送し、弱い参照でメモリ漏洩を防止し、handlerMessage()メソッドでsetCurrentItem()メソッドを呼び出し、現在のViewPagerのItemを対応するposition+1のデータに設定することで、外部でHandlerのsendMessage()メソッドを呼び出すだけで、ViewPagerを自動的に無限にマルチキャストできることがわかります.
    1.2 ViewPagerを最後の1枚から1枚目にスライドさせる
    ViewPagerは最後のページから最初のページにスライドできないことは知っていますが、別の考え方で、ViewPagerのAdapterでgetCount()メソッドでViewPagerのサイズを無限大に設定し、残りを取ることでスライドしたページがデータソースのデータに常に対応していることを保証すれば、ViewPagerが最後のページから最初のページにスライドする効果を実現することができます.
        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); //        
                imageView = new SimpleDraweeView(mContext);
                ((SimpleDraweeView) imageView).setImageURI(uri);
                container.addView(imageView);
                return imageView;
            }
            return null;
        }
    

    二、最適化の方法
    上記では単純にViewPagerの自動マルチキャスト機能を実現しただけですが、実際には最適化が必要な詳細がたくさんあります.例えば、ViewPagerのサイズを無限大に設定することで、最後の1枚から最初の1枚にスライドすることを実現していますが、このときキャッシュしないと、AdapterのinstantiateItem(ViewGroup container,final int position)メソッドでは、多くの新しいnewから出てきたViewを返す必要があります.これにより、不要なメモリの浪費が発生します.これらの詳細を最適化してこそ、コントロールがより使いやすくなり、安定性と性能の面でも優れています.
    2.1キャッシュによるメモリロスの削減
    ViewPagerがワイヤレスマルチキャスト機能を実現できるように、getCount()のサイズを無限大に設定することで実現しましたが、AdapterのinstantiateItem()メソッドで多くの新しいnewから出てきたViewを返すことで、不要なメモリの浪費を招く問題が発生します.
    したがって、リストをキャッシュプールとして使用し、AdapterのdestroyItem()メソッドで廃棄されたobjectをキャッシュプールに再利用することで、メモリの浪費を回避することができます.
        private final ArrayList mViewCaches = new ArrayList<>();
    
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            ImageView imageView = (ImageView) object;
            container.removeView(imageView);
            mViewCaches.add(imageView);
        }
    

    そしてAdapterのinstantiateItem()メソッドでは既にキャッシュされているViewをListから取り出して再利用する
        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 {
                    //          ,    
                    imageView = (ImageView) mViewCaches.remove(0);
                }
        }
    

    2.2自動ローテーションの適切な停止
    Bannerをタッチしたり、現在展示されているBannerのページを離れたりした場合、bannerが無線でマルチキャストを続けていると不要な性能損失をもたらすため、Bannerをタッチしたり、現在のActivityが非表示の場合、Bannerのマルチキャストを停止して性能を向上させる必要があります.
        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 ViewPager切替速度の変更
    オリジナルのViewPagerは自動ローテーションを行う場合、切替速度が非常に速く、突兀な感じがします.また、ViewPagerはインタフェースを提供していません.ViewPagerを切替速度の設定を行うので、反射的にScrollerを使って切替速度の設定を行い、Bannerをより滑らかにする必要があります.
        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);
        }
    }
    

    これで、私たちの自動マルチキャストコントロールは、性能的にも安定性的にも優れています.