Android--帶位置提示的輪播控件

github地址:https://github.com/zerohuan/SlideLayout/tree/master


實際效果圖:



該自定義控件繼承FrameLayout, 包含一個ViewPager和橫向排列的LinearLayout。後者用於包含顯示錶示輪播位置的點集,使用ViewPager的好處在於可以靈活的定義item的內容,而不僅僅是圖片。</p><p></p><p>爲了便於使用,通過自定義屬性的方式定義了所須的運行參數:</p><p><pre name="code" class="html"><declare-styleable name="SlideLayout">
        <attr name="viewpagerId" format="reference"/>
        <attr name="dotsId" format="reference"/>
        <attr name="autoPlay" format="boolean" />
        <attr name="slide_interval" format="integer" />
        <attr name="dotRadius" format="dimension" />
        <attr name="onDotColor" format="color" />
        <attr name="offDotColor" format="color" />
        <attr name="strokeColor" format="color" />
    </declare-styleable>

對應的數據成員如下:


    //輪播容器viewPager
    private ViewPager viewPager;
    //標示位置的點集
    private LinearLayout dots;
    //viewPager的資源ID
    private int viewPagerId;
    //包含點的LinearLayout的資源ID
    private int dotsId;
    //該手機的px-dp比例倍數
    private float scale;
    //ViewPager的Adapter
    private PagerAdapter adapter;
    //是否自動播放
    private boolean isAutoPlay;
    //播放的間隔
    private int interval;
    //當前頁位置
    private int currentItem;
    //圓點半徑
    private float dotRadius;
    //是否正在輪播運行
    private boolean isRunning;
    //位於當前頁,點標誌的顏色
    private int onDotColor;
    //未位於當前頁,點標誌的顏色
    private int offDotColor;
    //點邊框顏色
    private int strokeColor;

    private final static int SCROLL_WHAT = 0x7549;


使用時,通過Xml文件來定義:

<com.luckymore.ydd.app.view.selfView.SlideLayout
                xmlns:attrs="http://schemas.android.com/apk/res-auto"
                android:id="@+id/news_slide_bar"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                swipe:dotsId="@+id/test_1"
                attrs:viewpagerId="@+id/test_2"
                attrs:autoPlay="true"
                attrs:dotRadius="3dp"
                attrs:slide_interval="4000"
                attrs:offDotColor="@color/alpha_black"
                attrs:onDotColor="@color/alpha_white"
                attrs:strokeColor="#77626262"
                >

                <android.support.v4.view.ViewPager android:id="@+id/test_2"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" />

                <LinearLayout android:id="@+id/test_1"
                    android:layout_width="wrap_content"
                    android:layout_height="20dp"
                    android:orientation="horizontal"
                    android:layout_gravity="bottom|center_horizontal"
                    android:gravity="center"
                    android:paddingLeft="8dp"
                    android:paddingRight="8dp"
                    />

            </com.luckymore.ydd.app.view.selfView.SlideLayout>

獲取自定義參數值:

/**
     * 獲取自定義參數,在構造器中調用
     * @param context
     * @param attrs
     */
    private void init(Context context, AttributeSet...attrs) {
        if(attrs.length > 0) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs[0], R.styleable.SlideLayout);
            viewPagerId = typedArray.getResourceId(R.styleable.SlideLayout_viewpagerId, -1);
            dotsId = typedArray.getResourceId(R.styleable.SlideLayout_dotsId, -1);
            scale = getResources().getDisplayMetrics().density;
            isAutoPlay = typedArray.getBoolean(R.styleable.SlideLayout_autoPlay, false);
            interval = typedArray.getInteger(R.styleable.SlideLayout_slide_interval, 4000);
            dotRadius = typedArray.getDimension(R.styleable.SlideLayout_dotRadius, 2f * scale);
            onDotColor = typedArray.getColor(R.styleable.SlideLayout_onDotColor, 0x77FFFFFF);
            offDotColor = typedArray.getColor(R.styleable.SlideLayout_offDotColor, 0x77000000);
            strokeColor = typedArray.getColor(R.styleable.SlideLayout_strokeColor, 0x77626262);
        }
    }

ViewPager的ID和LinearLayout的ID通過自定義屬性的方式傳入,注入到SlideLayout中,在onFinishInflate中實現

@Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if(viewPagerId != -1 && dotsId != -1) {
            viewPager = (ViewPager)findViewById(viewPagerId);
            dots = (LinearLayout)findViewById(dotsId);

            viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
                @Override
                public void onPageScrolled(int i, float v, int i2) {

                }

                @Override
                public void onPageSelected(int i) {
                    currentItem = i;
                    for(int j = 0; j < viewPager.getAdapter().getCount(); j++) {
                        DotView dot = (DotView)dots.getChildAt(j);
                        if(j == i)
                            dot.setOn(true);
                        else
                            dot.setOn(false);
                        dot.invalidate();
                    }
                }

                @Override
                public void onPageScrollStateChanged(int i) {
                    switch (i) {
                        case 1:// 手勢滑動,空閒中
                            isAutoPlay = false;
                            break;
                        case 2:// 界面切換中
                            isAutoPlay = true;
                            break;
                        case 0:// 滑動結束,即切換完畢或者加載完畢
                            // 當前爲最後一張,此時從右向左滑,則切換到第一張
                            if (viewPager.getCurrentItem() == viewPager.getAdapter().getCount() - 1 && !isAutoPlay) {
                                viewPager.setCurrentItem(0);
                            }
                            // 當前爲第一張,此時從左向右滑,則切換到最後一張
                            else if (viewPager.getCurrentItem() == 0 && !isAutoPlay) {
                                viewPager.setCurrentItem(viewPager.getAdapter().getCount() - 1);
                        }
                            break;
                    }
                }
            });
        }
    }

接着實現,LinearLayout中的“點”的填充,首先定義內部類DotView:

/**
     * 點狀UI, 正方形View, 包含一個圓形點
     */
    public class DotView extends View {
        private Paint mPaint = new Paint();
        //兩種狀態, 是否是當前頁
        private boolean isOn;
        //正方形邊長
        private int mSize;

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

        public DotView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        public DotView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }

        /**
         * 比onDraw先執行
         * @param widthMeasureSpec
         * @param heightMeasureSpec
         */
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            mSize = (int)Math.ceil(dotRadius) * 3;
            setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
        }

        private int measureWidth(int measureSpec) {
            int result = 0;
            int specMode = MeasureSpec.getMode(measureSpec);
            int specSize = MeasureSpec.getSize(measureSpec);

            if (specMode == MeasureSpec.EXACTLY) {
                // We were told how big to be
                result = specSize;
            } else {
                // Measure the text
                result = mSize + getPaddingLeft() + getPaddingRight();
                if (specMode == MeasureSpec.AT_MOST) {
                    // Respect AT_MOST value if that was what is called for by
                    // measureSpec
                    result = Math.min(result, specSize);// 60,480
                }
            }

            return result;
        }

        private int measureHeight(int measureSpec) {
            int result = 0;
            int specMode = MeasureSpec.getMode(measureSpec);
            int specSize = MeasureSpec.getSize(measureSpec);

            if (specMode == MeasureSpec.EXACTLY) {
                // We were told how big to be
                result = specSize;
            } else {
                // Measure the text (beware: ascent is a negative number)
                result = mSize + getPaddingTop() + getPaddingBottom();
                if (specMode == MeasureSpec.AT_MOST) {
                    // Respect AT_MOST value if that was what is called for by
                    // measureSpec
                    result = Math.min(result, specSize);
                }
            }
            return result;
        }

        //繪製點
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            mPaint.setAntiAlias(true);
            if(isOn()) {
                mPaint.setColor(onDotColor);
            } else {
                mPaint.setColor(offDotColor);
            }
            mPaint.setStyle(Paint.Style.FILL);
            canvas.drawCircle(dotRadius, dotRadius, dotRadius, mPaint);

            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setStrokeWidth(0.7f * scale);
            mPaint.setColor(strokeColor);
            canvas.drawCircle(dotRadius, dotRadius, dotRadius, mPaint);
        }

        public boolean isOn() {
            return isOn;
        }

        public void setOn(boolean isOn) {
            this.isOn = isOn;
        }
    }

DotView包含是否是當前頁兩種狀態,填充顏色和邊框顏色都從自定義屬性中獲得。


在SlideLayout的成員函數resetDots填充DotView:

/**
     * 重新生成點集UI
     */
    public void resetDots() {
        int dotCount = viewPager.getAdapter().getCount();
        int oldDotCount = dots.getChildCount();
        for(int i = 0; i < dotCount; i ++) {
            DotView dot;
            if(i >= oldDotCount) {
                dot = new DotView(getContext());
                dots.addView(dot);
            } else {
                dot = (DotView)dots.getChildAt(i);
            }
            if(i == viewPager.getCurrentItem()) {
                dot.setOn(true);
            } else {
                dot.setOn(false);
            }
        }
    }

當LinearLayout點集的dotView數量小於ViewPager中Pager數量時,新建DotView,填充至LinearLayout;


在UI線程中使用SlideLayout主要調用如下兩個方法:

/**
     * 在UI線程中調用,注入ViewPager的adapter
     * @param adapter
     */
    public void setViewPagerAdapter(PagerAdapter adapter) {
        this.adapter = adapter;
        viewPager.setAdapter(adapter);
        updateUI();
    }

    /**
     * 在UI線程中調用,修改adapter中數據後, 更新UI
     */
    public synchronized void updateUI() {
        adapter.notifyDataSetChanged();
        resetDots();
        if(isAutoPlay)
            startAutoScroll();
    }


最後,實現定時輪播,通過Handler&Message消息隊列機制來實現輪播, 利用sendEmptyMessageDelayed來實現輪播間隔:

private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            switch (msg.what) {
                case SCROLL_WHAT:
                    synchronized(SlideLayout.this) {
                        viewPager.setCurrentItem(currentItem);
                        sendScrollMessage(interval);
                    }
                    break;
            }
        }
    };

    /**
     * 開始自動輪播
     */
    public void startAutoScroll() {
        if(!isRunning) {
            isRunning = true;
            sendScrollMessage(interval);
        }
    }

    /**
     * 滾動到下一頁
     * @param delayTimeInMills
     */
    private synchronized void sendScrollMessage(long delayTimeInMills) {
        /** remove messages before, keeps one message is running at most **/
        handler.removeMessages(SCROLL_WHAT);
        currentItem = (currentItem + 1) %  adapter.getCount();
        //通過該方法實現定時輪播
        handler.sendEmptyMessageDelayed(SCROLL_WHAT, delayTimeInMills);
    }


最後,在UI線程中調用一句代碼,可以使用輪播控件:

newsBar.setViewPagerAdapter(new NewsBarAdapter(ret.getNews(), getMactivity()));










發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章