show code block

2017年11月22日 星期三

Android元件(Indicator) – 指示器Indicator,ViewPager下方的圓點

前言


目前市面上已經很多Banner的第三方可以使用了
甚至是已經套好的ViewPager第三方 和Banner第三方。



這邊提供一個我自己常用的Indicator第三方
https://github.com/romandanylyk/PageIndicatorView

可以下載我的ViewPager demo 自己套套看這個第三方

情況是這樣的:
前幾天我使用Fragment,發現如果同時創建兩個相同的Fragment但不同內容的頁面,這個套件的Indicator會指向同一個id

 簡而言之就是兩個Banner會共用到同一個Indicator


只好自己寫一個Indicator來用
ViewPager Banner 內的指示器




本日實作


先下載ViewPagerDemo我直接在這篇加內容


首先呢 先下載制作Indicator的圖片
分別是
圖片一

圖片二

有看到東西嗎?

沒看到沒關係,因為我也沒看到XD  本來就是白色的

在這講一下,一個是空心圓 一個是實心圓

下載的地方幫你擺在這 點我下載


改動的部分
綠色為新增的

一、先把你剛剛下載的圖片放到drawable內

此時你會注意看到圖片內有一個xml檔案叫indicator_selector.xml


這其實可以自己創

每個item後面就是這張圖片被按到後的狀態
state_selected是被選取的狀態,true 是被選到,false則相反
看上圖,可以看出當true(被選取時),是實心圓,false(沒被選取)則是空心圓

這種寫法同樣可以套用在按鈕上,使用TextView設計一個按鈕,按下時的和非按下時會顯示不一樣的圖。詳情見:http://nikeru8.blogspot.tw/2017/03/textview.html


二、創建attrs.xml



命名為 attrs

attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="indicatorIcon" format="reference" />
    <attr name="indicatorMargin" format="dimension" />
    <attr name="indicatorSmooth" format="boolean" />
    <attr name="indicatorSize" format="dimension" />

    <declare-styleable name="IndicatorView">
        <!--指示器樣式-->
        <attr name="indicatorIcon" />
        <!--指示器的間距-->
        <attr name="indicatorMargin" />
        <!--指示器是否顯示滑動效果-->
        <attr name="indicatorSmooth" />
        <!--指示器的大小-->
        <attr name="indicatorSize" />
    </declare-styleable>

</resources> 

這裡是幹嘛的呢?
讓我們把畫面轉到activity_main.xml稍微更改了一下xml

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="250dp">

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


        <com.hello.kaiser.viewpager.IndicatorView
            android:id="@+id/id_view_indicator"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:layout_gravity="center_horizontal|bottom"
            android:layout_marginBottom="32dp"
            app:indicatorSmooth="false"
            custom:indicatorIcon="@drawable/indicator_selector"
            custom:indicatorMargin="5dp"
            custom:indicatorSize="9dp" />
    </RelativeLayout>
</RelativeLayout>
 

此時你會發現 在最外層的RelativeLayout 內
多了一個  xmlns:custom="http://schemas.android.com/apk/res-auto"
然後再看看新多的IndicatorView
是不是有剛剛在attrs內新增的參數了,indicatorIconindicatorMargin ...這類的

那要如何使用它呢?

創一個IndicatorView.java
public class IndicatorView extends View implements ViewPager.OnPageChangeListener{

    private Drawable mIndicator; //指示器的显示样式
    private int mIndicatorSize; //指示器的大小
    private boolean mSmooth; //是否显示滑动效果
    private int mMargin;  //指示器之间的间距

    private int mDefaultMargin; //默认的间距
    private int mSelectedItem; //选中的指示器

    private int mWidth; //测量后得到的控件的宽度
    private int mCount; //viewpager的页数,即指示器的个数
    private int mContextWidth; //实际计算得到的控件的宽度
    private float mOffset; //滑动偏移量


    private ViewPager.OnPageChangeListener mPageChangeListener;

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

    public IndicatorView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

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

    private void init(Context context, AttributeSet attrs, int defStyleAttr) {

        //设置指示器大小的默认值
        mIndicatorSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,16,getResources().getDisplayMetrics());
        //设置指示器间距的默认值
        mDefaultMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,5,getResources().getDisplayMetrics());

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IndicatorView,defStyleAttr,0);

        mIndicator = typedArray.getDrawable(R.styleable.IndicatorView_indicatorIcon);
        mMargin = (int) typedArray.getDimension(R.styleable.IndicatorView_indicatorMargin,mDefaultMargin);
        mSmooth = typedArray.getBoolean(R.styleable.IndicatorView_indicatorSmooth,true);
        mIndicatorSize = (int) typedArray.getDimension(R.styleable.IndicatorView_indicatorSize,mIndicatorSize);

        typedArray.recycle();

        mIndicator.setBounds(0,0,mIndicatorSize,mIndicatorSize);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
    }

    /**
     * 测量宽
     * @param widthMeasureSpec
     * @return
     */
    private int measureWidth(int widthMeasureSpec) {
        final int mode = MeasureSpec.getMode(widthMeasureSpec);
        final int size = MeasureSpec.getSize(widthMeasureSpec);

        int width;
        //计算整个控件的宽度,其中 mCount 是获取到的ViewPager的页数
        int desired = getPaddingLeft() + mIndicatorSize * mCount + mMargin * (mCount - 1) + getPaddingRight();
        mContextWidth = desired;

        if (mode == MeasureSpec.EXACTLY) {
            //如果是match_parent,则选取屏幕宽度和计算得到的宽度中较大的那个作为控件的宽度
            width = Math.max(desired,size);
        }else {
            if (mode == MeasureSpec.AT_MOST) {
                //如果是wrap_parent,则选取屏幕宽度和计算得到的宽度中较小的那个作为控件的宽度
                width = Math.min(desired,size);
            }else {
                //还有一种情况是 UNSPECIFIED ,一般是系统内部测量的过程中使用,这种情况也可以忽略掉
                width = desired;
            }
        }

        mWidth = width;
        return width;
    }

    /**
     * 测量高
     * @param heightMeasureSpec
     * @return
     */
    private int measureHeight(int heightMeasureSpec) {
        final int mode = MeasureSpec.getMode(heightMeasureSpec);
        final int size = MeasureSpec.getSize(heightMeasureSpec);

        int height;
        int desired = getPaddingTop() + mIndicatorSize + getPaddingBottom();

        if (mode == MeasureSpec.EXACTLY) {
            height = size;
        }else {
            if (mode == MeasureSpec.AT_MOST) {
                height = Math.min(desired,size);
            }else {
                height = desired;
            }
        }

        return height;
    }

    @Override
    protected void onDraw(Canvas canvas) {

        canvas.save();
        /**
         * 这里是计算绘制控件的x轴上的起点位置
         * 当wrap_content并且控件宽度小于屏幕宽度时,计算得到的宽度和实际宽度相同,mWidth/2 - mContextWidth/2 = 0,这种情况很好理解
         * 再看当match_content的情况,控件的计算宽度小于屏幕宽度,则mWidth > mContextWidth,则计算得到的left是计算得到的控件距离屏幕左端的距离,
         * 控件将绘制在屏幕水平方向的中间,不清楚可以在纸上画下,其他情况也类似
         */
        int left = mWidth/2 - mContextWidth/2 + getPaddingLeft();
        canvas.translate(left,getPaddingTop());
        //依次绘制指示器
        for (int i = 0 ; i < mCount ; i++) {
            mIndicator.setState(EMPTY_STATE_SET);
            mIndicator.draw(canvas);
            canvas.translate(mIndicatorSize + mMargin,0);
        }
        canvas.restore();

        /**
         * 下面是绘制选中的指示器的样式
         */
        float leftDraw = ( mIndicatorSize + mMargin ) * ( mSelectedItem + mOffset );
        canvas.translate(left,getPaddingTop());
        canvas.translate(leftDraw,0);
        mIndicator.setState(SELECTED_STATE_SET);
        mIndicator.draw(canvas);

    }

    public void setViewPager(ViewPager viewPager) {
        if (viewPager == null) {
            return;
        }
        PagerAdapter pagerAdapter = viewPager.getAdapter();
        if (pagerAdapter == null) {
            throw new RuntimeException("请先设置viewpager的adaper");
        }
        mCount = pagerAdapter.getCount();
        viewPager.addOnPageChangeListener(this);
        mSelectedItem = viewPager.getCurrentItem();
        invalidate();
    }

    /**
     * 因为自定义控件注册了viewpager的滑动监听,
     * 为了外部能够同样监听到滑动事件,所以提供该接口
     * @param pageChangeListener
     */
    public void setOnPageChangeListener(ViewPager.OnPageChangeListener pageChangeListener) {
        this.mPageChangeListener = pageChangeListener;
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        if (mSmooth){
            //允许显示滑动效果
            mSelectedItem = position;
            mOffset = positionOffset;
            //通知view重绘
            invalidate();
        }
        if (mPageChangeListener != null) {
            mPageChangeListener.onPageScrolled(position,positionOffset,positionOffsetPixels);
        }
    }

    @Override
    public void onPageSelected(int position) {
        //当有page被选中后通知view重绘
        mSelectedItem = position ;
        invalidate();

        if(mPageChangeListener != null){
            mPageChangeListener.onPageSelected(position);
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        if(mPageChangeListener != null){
            mPageChangeListener.onPageScrollStateChanged(state);
        }
    }
}

 

使用方式

這時候就可以在activity_main.xml內使用Indicator
 <com.hello.kaiser.viewpager.IndicatorView
            android:id="@+id/id_view_indicator"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:layout_gravity="center_horizontal|bottom"
            android:layout_marginBottom="32dp"
            app:indicatorSmooth="false"
            custom:indicatorIcon="@drawable/indicator_selector"
            custom:indicatorMargin="5dp"
            custom:indicatorSize="9dp" />
 
這裡要注意的事,如果你是用複製貼上的方式在看這篇BLOG
你會發現會報錯,因為你寫的IndicatorView位置和我的並不一樣
我寫在com.hello.kaiser.viewpager.IndicatorView內,你必須指定你自己的IndicatorView才有辦法使用

在MainActivity.java內,設定IndicatorView的ViewPager吧
 IndicatorView indicatorView = (IndicatorView) findViewById(R.id.id_view_indicator);
 indicatorView.setViewPager(mViewPager);
 



DEMO
https://github.com/nikeru8/ViewPagerDemo/tree/AddIndicator




沒有留言:

張貼留言

協程(coroutine) - 協程為什麼要學它?

 Coroutine 協程 再強調一次 協程就是由kotlin官方所提供的線程api //Thread Thread { }.start() //Executor val execu...