カスタムマルチキャストコントロールBannerView

13619 ワード

一、紹介
プロジェクトで使用される自動マルチキャストコントロールは、ネット上で他の人がやっているので、問題が発生したときにコードの詳細を見て雷を掃くのは時間の無駄です.そこで自分で車輪を作ったことを痛感した.このコントロールはappで非常に頻繁に使用され、原理も複雑ではなく、前後に1ページずつ追加されます.android開発者一人一人がこれを作ると信じています.機能紹介:1.無限自動ローテーション.2.インジケータ(下の小さな点)3.スクロールアニメーションの時間調整4.ドラッグしている間に輪番を止めます
じつこうかず
すべてのコードとサンプルコードがGitHubにアップロードされました.https://github.com/CuteWen/BannerView興味があれば降りてみてください.
二、実現
まず1つのViewをカスタマイズしてViewPagerを継承してそれから私达の自动的な轮播の実现の肝心な点は実はすべてPagerAdapterの中で、私达は自分で1つのPagerAdapterをカプセル化することができて、しかし自分のカプセル化のAdapterは使用者に论理を书く时あなたのadapterがどの程度カプセル化したことを理解させて、どんな方法を放して、个人はそんなに好きではありませんだから私はここで装飾者モードを使って使用者が書いたAdapterを拡張して、このように使う時最も普通のPagerAdapterを書くだけで自動放送の機能を付加することができます.
注:装飾者のパターンがよく分からない学生はここに行って見てもいいです.中には説明がいいです.https://www.cnblogs.com/chenxing818/p/4705919.html
1.包装類
私たちが包装する必要がある機能を考えてみると、ページ数+2、主にgetCountという方法です.また、2つのアダプタ間のposition変換の方法を書いて、これらの方法を統一的に呼び出すことで論理的な混乱を避けることができます.次は私たちの包装類です.
/**
     *        ---------------------------------------------------------
     */
    private class BannerAdapterWrapper extends PagerAdapter {
        private PagerAdapter pagerAdapter;

        public BannerAdapterWrapper(PagerAdapter pagerAdapter) {
            this.pagerAdapter = pagerAdapter;
        }

        @Override
        public int getCount() {
            return pagerAdapter.getCount() > 1 ? pagerAdapter.getCount() + 2 : pagerAdapter.getCount();
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view.equals(object);
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            return pagerAdapter.instantiateItem(container, bannerToAdapterPosition(position));
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            pagerAdapter.destroyItem(container, position, object);
        }

        /**
         *     position    position   
         */
        public int bannerToAdapterPosition(int position) {
            int adapterCount = pagerAdapter.getCount();
            if (adapterCount <= 1) return 0;
            int adapterPosition = (position - 1) % adapterCount;
            if (adapterPosition < 0) adapterPosition += adapterCount;
            return adapterPosition;
        }

        public int toWrapperPosition(int position) {
            return position + 1;
        }
    }

主にしました:1.getCountの上限に2を加えたのが前後各1ページの役割です.2.2つのアダプタ間のposition間の変換方法を書いて呼び出しやすい.
2.陳倉(AdapterWrapper)の後の後始末
setAdapterメソッドを見てみましょう.
 /**
     *               
     */
    @Override
    public void setAdapter(PagerAdapter adapter) {
        this.adapter = adapter;
        //            
        this.adapter.registerDataSetObserver(new BannerPagerObserver());
        //        
        bannerAdapterWrapper = new BannerAdapterWrapper(adapter);
        //     adapter        
        super.setAdapter(bannerAdapterWrapper);
        //         (       )
        addOnPageChangeListener(new BannerPageChangeListener());
        //   handler       (       )
        looperHandler = new LooperHandler(this);
    }

ここにはDataSetObserverが登録されています.これは普段使われているものが少なく、Adapterを傍受するために使われています.notifyDataSetChanged()の.実際にBannerViewをバインドしているのはWrapperの後のアダプタadapterであり、使用者が元のadapterのnotifyDataSetChanged()を呼び出しているので、転送プロセスが必要です!
/**
     *            -----------------------------------------------------
     */
    private class BannerPagerObserver extends DataSetObserver {

        @Override
        public void onChanged() {
            super.onChanged();
            dataSetChanged();
        }

        @Override
        public void onInvalidated() {
            super.onInvalidated();
            dataSetChanged();
        }
    }

    /**
     *       
     */
    private void dataSetChanged() {
        if (bannerAdapterWrapper != null && pagerAdapter.getCount() > 0) {
            bannerAdapterWrapper.notifyDataSetChanged();
            bannerIndicatorView.setCount(pagerAdapter.getCount());
            setCurrentItem(0);
        }
    }

同様にsetCurrentItem()メソッドを呼び出すときもpositionは異なります.

    @Override
    public void setCurrentItem(int item, boolean smoothScroll) {
        super.setCurrentItem(bannerAdapterWrapper.toWrapperPosition(item), smoothScroll);
    }

    @Override
    public void setCurrentItem(int item) {
        super.setCurrentItem(bannerAdapterWrapper.toWrapperPosition(item));
    }

    @Override
    public int getCurrentItem() {
        return bannerAdapterWrapper.bannerToAdapterPosition(super.getCurrentItem());
    }

3.ページをめくって傍受する
/**
    *     ----------------------------------------------------------------
    */
  private class BannerPageChangeListener implements OnPageChangeListener {

       @Override
       public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

       }

       @Override
       public void onPageSelected(int position) {
           //         
           if (bannerIndicatorView != null) {
               bannerIndicatorView.setSelect(bannerAdapterWrapper.bannerToAdapterPosition(position));
           }
       }

       @Override
       public void onPageScrollStateChanged(int state) {
           int position = BannerView.super.getCurrentItem();
           //        
           if (state == ViewPager.SCROLL_STATE_IDLE &&
                   (position == 0 || position == bannerAdapterWrapper.getCount() - 1)) {
               setCurrentItem(bannerAdapterWrapper.bannerToAdapterPosition(position), false);
           }
           //                
           if (state == ViewPager.SCROLL_STATE_IDLE) {
               if (timer == null) {
                   timer = new Timer();
                   timer.schedule(new TimerTask() {
                       @Override
                       public void run() {
                           looperHandler.sendEmptyMessage(0);
                       }
                   }, intervalTime + scrollTime, intervalTime + scrollTime);
               }
           } else if (state == ViewPager.SCROLL_STATE_DRAGGING) {
               if (timer != null) {
                   timer.cancel();
                   timer = null;
               }
           }
       }
   }

中の同期インジケータと自動マルチキャスト停止コードはしばらく表にありません.主に無限マルチキャストのジャンプのコードが「無限」の実現を完了することである.
4.自動ローテーション
ここではTimer+Handlerの組み合わせを用いてタイミングスライドの動作を完了した.
    /**
     *           Timer  
     */
    public void setIntervalTime(int intervalTime) {
        this.intervalTime = intervalTime;
        timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                looperHandler.sendEmptyMessage(0);
            }
        }, intervalTime + scrollTime, intervalTime + scrollTime);
    }

    /**
     *       -------------------------------------------------------------------
     */
    private static class LooperHandler extends Handler {
        private WeakReference weakReference;

        public LooperHandler(BannerView bannerView) {
            this.weakReference = new WeakReference<>(bannerView);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            weakReference.get().setCurrentItem(weakReference.get().getCurrentItem() + 1);
        }
    }

また、スクロールする時間を設定します.ここでは、反射を使用してmScrollerというオブジェクトを修正する必要があります.
    /**
     *             
     */
    public void setScrollTime(int scrollTime) {
        try {
            Field field = ViewPager.class.getDeclaredField("mScroller");
            field.setAccessible(true);
            FixedSpeedScroller scroller = new FixedSpeedScroller(getContext(),
                    new AccelerateInterpolator());
            field.set(this, scroller);
            scroller.setScrollDuration(scrollTime);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

/**
     *   ViewPager       -----------------------------------------------------------
     */
    private class FixedSpeedScroller extends Scroller {
        private int duration = 300;

        public FixedSpeedScroller(Context context, Interpolator interpolator) {
            super(context, interpolator);
        }

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

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

        public void setScrollDuration(int duration) {
            this.duration = duration;
        }
    }

5.インジケータ
先着コード
public class BannerIndicatorView extends View {
    private int count;
    private int select;

    private Paint pointPaint;
    private Paint selectPaint;
    private String selectColor = "#FFFFFF";
    private String normalColor = "#80FFFFFF";

    private int radius = 10;
    private int interval = 10;

    public BannerIndicatorView(Context context) {
        this(context, null);
    }

    public BannerIndicatorView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BannerIndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        pointPaint = new Paint();
        pointPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        pointPaint.setColor(Color.parseColor(normalColor));
        pointPaint.setStyle(Paint.Style.FILL_AND_STROKE);

        selectPaint = new Paint();
        selectPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        selectPaint.setColor(Color.parseColor(selectColor));
        selectPaint.setStyle(Paint.Style.FILL_AND_STROKE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //         
        for (int i = 0; i < count; i++) {
            if (i == select) {
                canvas.drawCircle(radius + i * (radius * 2 + interval), getHeight() / 2, radius, selectPaint);
            } else {
                canvas.drawCircle(radius + i * (radius * 2 + interval), getHeight() / 2, radius, pointPaint);
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = count * radius * 2 + (count - 1) * interval;
        int height = radius * 2;
        widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    /**
     *         ,    
     */
    public void setSelect(int select) {
        this.select = select;
        invalidate();
    }

    /**
     *     
     */
    public void setCount(int c) {
        count = c;
    }

    public void setSelectColor(String selectColor) {
        this.selectColor = selectColor;
    }

    public void setNormalColor(String normalColor) {
        this.normalColor = normalColor;
    }
}

この部分は比較的簡単で、いくつかの白い小さな円点を描き、setSelectの方法を提供して選択中の点を変化させることです.
そしてBannerViewにsetIndicator()と書く方法
    /**
     *      ,   setAdapter  
     */
    public void setIndicator(BannerIndicatorView bannerIndicatorView) {
        this.bannerIndicatorView = bannerIndicatorView;
        if (pagerAdapter != null) {
            bannerIndicatorView.setCount(pagerAdapter.getCount());
        }
    }

3:例とすべてのコード
XMLの書き方例:
    

    

注意:android:layout_centerHorizonta=「true」はポイントを中央にするためです.
class BannerActivity : AppCompatActivity() {
    var bannerView: BannerView? = null
    var indicatorView: BannerIndicatorView? = null
    var adapter: BannerAdapter? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_banner)
        bannerView = findViewById(R.id.bv_activity_banner) as BannerView
        indicatorView = findViewById(R.id.biv_activity_banner) as BannerIndicatorView
        adapter = BannerAdapter(this)
        //   adapter
        bannerView?.adapter = adapter
        //      
        bannerView?.setIndicator(indicatorView)
        //        
        bannerView?.setScrollTime(500)
        //       
        bannerView?.setIntervalTime(3000)
        val data:ArrayList = ArrayList()
        data.add("1111")
        data.add("2222")
        data.add("1111")
        data.add("2222")
        adapter?.addData(data)
    }
}

この部分はkotlinで書いてありますが、呼び出しはこのいくつかの方法で、読めないところはないはずです.