天天看點

Android自定義View使用canvas實作輪播圖效果

1.功能分析

1.1 左右滑動切換圖檔,并且實作循環切換。

1.2 自動切換圖檔

1.3 導航圓點跟随輪播變更

1.4 點選圖檔,實作監聽回報

1.5 圖檔需要适配螢幕,按定義寬高顯示

Android自定義View使用canvas實作輪播圖效果

2.代碼實作

2.1 實作原理

每次加載顯示需要3張圖檔,并且偏移至左中右三個位置,不斷地重繪view,修改偏移值,達到切換圖檔效果。

2.2 代碼實作

建立自定義View類CarouselFigure,在onMeasure方法中,擷取容器view的寬度,這裡使用預設顯示圖檔寬度與view容器寬度比值,作為适配比,然後确定容器view顯示高度。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //擷取view寬高
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec);
        //預設showindex對應圖檔适配後寬高,做輪播圖整體寬高
        if(imgList!=null && imgList.size()>){
            Bitmap bitmap = imgList.get(showIndex);
            float scale = (float)mWidth / bitmap.getWidth();
            mHeight = (int) (scale * bitmap.getHeight());
        }
        setMeasuredDimension(mWidth,mHeight);
    }
           

手指左右滑動圖檔時候,擷取橫向手指偏移量,偏移量具有方向,向左為負,向右為正。向左偏移,左中右圖檔動态偏移量分别為,-mWidth+offset ,offset,mWidth+offset。showIndex表示顯示圖檔imgList中索引号,pre_show_index為左圖索引号,next_show_index為右圖索引号。通過變更偏移和索引号,切換顯示圖檔。

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //判斷偏移之後在臨界值
        float min_x = mWidth-Math.abs(offset);
        //小于臨界值時,改變顯示圖檔索引值
        if(min_x<MIN_WIDTH_OFFSET_VALUE){
            //左移動
            if(offset< ){
                showIndex ++;
            }
            if(offset> ){
                showIndex--;
            }
            //回歸
            reSetValue();
        }
        //初始化showIndex
        initShowIndex();
        //構畫左中右三圖
        handleImgWnH(pre_show_index);
        handleImgWnH(showIndex);
        handleImgWnH(next_show_index);
        canvas.drawBitmap(imgList.get(pre_show_index),-mWidth+offset,,mPaint);
        canvas.drawBitmap(imgList.get(showIndex),offset,,mPaint);
        canvas.drawBitmap(imgList.get(next_show_index),mWidth+offset,,mPaint);
        //構畫導航圓點
        drawCycle(canvas);
    }
           

一開始觸摸圖檔,記錄目前觸摸坐标,标記為初始坐标startPoint,移動時擷取實時坐标,計算手勢滑動方向與水準方向夾角正切值,判斷是否是水準滑動,如果是橫向水準滑動,則記錄滑動偏移量,調用invalidate進行重繪

@Override
    public boolean onTouchEvent(MotionEvent event) {
        //正在重新整理禁止觸摸
        if(isRefreshing){return true;}
        //正在自動輪播時,不能觸發
        if(isAutoSliding||isGestureSliding){return true;}
        int action = event.getAction();
        switch (action){
            case MotionEvent.ACTION_DOWN:
                //關閉自動輪播
                isAllowAutoSlid = false;
                startPoint.set(event.getX(),event.getY());//記錄初始值
                return true;
            case MotionEvent.ACTION_MOVE:
                float min_x = Math.abs(startPoint.x - event.getX());
                float min_y = Math.abs(startPoint.y - event.getY());
                //擷取正切值
                float angle_tan_value = min_y / min_x;
                if(angle_tan_value<MIN_ANGLE_TAN_VALUE){ //橫向滑動
                    min_x = event.getX() - startPoint.x ;
                    offset = old_offset + min_x;
                    nowPoint.set(event.getX(),event.getY());//記錄目前坐标
                    invalidate();
                }
                return true;
            case MotionEvent.ACTION_UP:
                old_offset = offset;//記錄上一次偏移量
                //滑動手勢産生圖檔切換效果
                post(slidImgRunnable);
                //重新開啟自動輪播
                isAllowAutoSlid = true;
                if(offset==) {
                    onTopImageClickListeners.onClick(showIndex);
                }
                return true;
        }
        return false;

    }
           

此時,已經實作讓圖檔跟随手指偏移。但是圖檔并不會自動保持滑動慣性,使自己顯示完全。 是以,需要在ACTION_UP處補充一個方法,讓圖檔繼續完成剩下偏移。此處使用線程slidImgRunnable

private Runnable slidImgRunnable = new Runnable() {
        @Override
        public void run() {
            float abs_offset = Math.abs(offset);
            //向左移動
            if(offset<) {
                if(abs_offset<ALLOW_SLID_IMG_OFFSET){  //允許産生滑動的偏移量,否則圖檔回歸原位置
                    offset += SLID_IMG_INTERVAL_OFFSET;
                    if(offset>){
                        reSetValue();
                    }
                }else {
                    offset += -SLID_IMG_INTERVAL_OFFSET;
                }
            }else if(offset>){ //向右移動
                if(abs_offset<ALLOW_SLID_IMG_OFFSET){ //允許産生滑動的偏移量,否則圖檔回歸原位置
                    offset += -SLID_IMG_INTERVAL_OFFSET;
                    if(offset<){
                        reSetValue();
                    }
                }else {
                    offset += SLID_IMG_INTERVAL_OFFSET;
                }
            }
            //當滑動之後 offset重置為0 結束循環
            if(abs_offset==){
                isGestureSliding = false;
                return;
            }
            isGestureSliding = true;
            invalidate();
            postDelayed(slidImgRunnable,SLID_IMG_INTERVALS);
        }
    };
           

無論向左還是向右移動,offset 絕對值都在增大,并且offset區間在0~mWidth之間,是以,隻要設定一個遞增偏移常量,不斷循環執行修改偏移量和重繪,就可以讓圖檔自動完成偏移。

自動輪播效果,需要另啟一循環線程執行。autoSlidImg決定輪播周期,autoSlidImgRunnable 完成偏移量遞增,和重繪view

/**
     * 開啟自動輪播
     */
    private void autoSlidImg()  {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (true){
                        Thread.sleep();//每4秒輪播一次
                        if(isAllowAutoSlid){
                            post(autoSlidImgRunnable);
                        }
                    }
                }catch (Exception e ){
                }
            }
        }).start();
    }

    /**
     * 實作自動輪播效果
     */
    private Runnable autoSlidImgRunnable = new Runnable() {
        @Override
        public void run() {
            offset += -SLID_IMG_INTERVAL_OFFSET;
            if (isNextCycle) {//判斷是否可以繼續産生偏移,完成一次輪播後退出
                offset=;
                isNextCycle=false;
                isAutoSliding = false;
                return;
            }else{
                isAutoSliding = true;//正在輪播滑動
            }
            invalidate();
            postDelayed(autoSlidImgRunnable, SLID_IMG_INTERVALS);
        }
    };
           

定義接口傳回點選顯示圖檔監聽

/**
     *定義點選接口
     */
    public interface OnTopImageClickListeners{
         void onClick(int showIndex);
    }

    carouselFigure.setOnTopImageClickListeners(new  CarouselFigure.OnTopImageClickListeners() {
            @Override
            public void onClick(int showIndex) {
                Log.i("tag","index: "+showIndex);
            }
        });
           

github分享位址

CSDN下載下傳位址

繼續閱讀