自定義View — 頁面指示器

讀人就是讀自己。 — 《等一個(gè)人讀書》

寫在前面

最近項(xiàng)目又改了UI,真是一件開心的事情(微笑臉),效果圖見圖一,右上角有一個(gè)水平的白色線條,還有一個(gè)灰色的背景線條,就是這個(gè)東西,它是一個(gè)指示器。起初在沒看到代碼之前,我以為添加應(yīng)用的界面類似ScrollView這種東西做的,如圖二的淘寶首頁輪播圖下面部分,指示器和內(nèi)容聯(lián)動,手指滑動內(nèi)容,指示器就會實(shí)時(shí)隨之變化,具體效果詳見淘寶首頁。但是這里面有一個(gè)問題,我們項(xiàng)目這個(gè)界面用ViewPager寫的,所以解決方案是根據(jù)頁數(shù)去更新指示器位置。

圖一.jpg
圖二.jpg

具體實(shí)現(xiàn)

上面講明了需求,現(xiàn)在就讓我們用代碼實(shí)現(xiàn)該指示器,創(chuàng)建TrackView繼承自View。

public class TrackView extends View {

    // 背景色
    private int mBackColor;
    // 前景色
    private int mForeColor;
    // 背景寬度
    private int mBackWidth;
    // 前景寬度
    private int mForeWidth;
    // 高度
    private int mHeight;
  
    // 前景色距View開始距離
    private float mForeDistance;

    // 畫筆
    private Paint mPaint;

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

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

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

    private void init(AttributeSet attrs) {
        if (null != attrs) {
            // 獲取自定義屬性,獲取不到則使用默認(rèn)值
            TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.TrackView);
            mBackColor = typedArray.getColor(R.styleable.TrackView_back_color, Color.GRAY);
            mForeColor = typedArray.getColor(R.styleable.TrackView_fore_color, Color.WHITE);
            mBackWidth = typedArray.getDimensionPixelOffset(R.styleable.TrackView_back_width, 100);
            mForeWidth = typedArray.getDimensionPixelOffset(R.styleable.TrackView_fore_width, 50);
            mHeight = typedArray.getDimensionPixelOffset(R.styleable.TrackView_height, 10);
            // 一定要回收
            typedArray.recycle();
        } else {
            mBackColor = Color.GRAY;
            mForeColor = Color.WHITE;
            mBackWidth = 100;
            mForeWidth = 50;
            mHeight = 10;
        }
        
        // 創(chuàng)建畫筆,設(shè)置抗鋸齒
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        // 設(shè)置畫筆開始和結(jié)束為圓角
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        // 設(shè)置畫筆寬度
        mPaint.setStrokeWidth(mHeight);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawBackground(canvas);
        drawForeground(canvas);
    }

    /**
     * 繪制背景線條,固定的那條線
     * 需要考慮內(nèi)邊距
     * @param canvas
     */
    private void drawBackground(Canvas canvas) {
        mPaint.setColor(mBackColor);
        canvas.drawLine(mHeight + getPaddingLeft(),
                mHeight / 2 + getPaddingTop(),
                mHeight + getPaddingLeft() + mBackWidth,
                mHeight / 2 + getPaddingTop(),
                mPaint);
    }

    /**
     * 繪制前景線條,會動的那條線,根據(jù)mForeDistance改變位置
     * 需要考慮內(nèi)邊距
     * @param canvas
     */
    private void drawForeground(Canvas canvas) {
        mPaint.setColor(mForeColor);
        canvas.drawLine(mHeight + getPaddingLeft() + mForeDistance,
                mHeight / 2 +  getPaddingTop(),
                mHeight + getPaddingLeft() + mForeDistance + mForeWidth,
                mHeight / 2 + getPaddingTop(),
                mPaint);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 重新計(jì)算寬高
        int width = getSize(mHeight * 2 + mBackWidth + getPaddingLeft() + getPaddingRight(), widthMeasureSpec);
        int height = getSize(mHeight + getPaddingTop() + getPaddingBottom(), heightMeasureSpec);
        setMeasuredDimension(width, height);
    }

    private int getSize(int size, int measureSpec) {
        int result = size;
        int mode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        switch (mode) {
            // 如果測量模式為未知或wrap_content,則返回默認(rèn)值。
            case MeasureSpec.UNSPECIFIED:
            case MeasureSpec.AT_MOST:
                result = size;
                break;
            // 如果測量模式為具體數(shù)值或match_parent,則返回具體數(shù)值。
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            default:
                break;
        }
        return result;
    }

    /**
     * 根據(jù)頁數(shù)更新指示器位置
     * @param position 當(dāng)前頁數(shù)
     * @param position 總頁數(shù)
     */
    public void updateByPage(int position, int count) {
        float offset = mBackWidth - mForeWidth;
        mForeDistance = offset / (count - 1) * position;
        postInvalidate();
    }
}

下面是自定義屬性,在/src/main/res/values/attrs.xml中。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="TrackView">
        <attr name="back_color" format="color"/>
        <attr name="fore_color" format="color"/>
        <attr name="back_width" format="dimension"/>
        <attr name="fore_width" format="dimension"/>
        <attr name="height" format="dimension"/>
    </declare-styleable>
</resources>

如何使用

下面通過一個(gè)Demo演示如何使用該自定義View。

1.創(chuàng)建布局

使用ConstraintLayout包裹ViewPager和TrackView,指定TrackView的自定義屬性。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

    <com.chad.learning.track.view.TrackView
        android:id="@+id/view_track"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        app:back_color="@android:color/darker_gray"
        app:back_width="100dp"
        app:fore_color="@android:color/background_dark"
        app:fore_width="50dp"
        app:height="10dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

</android.support.constraint.ConstraintLayout>
2.創(chuàng)建適配器

ViewPager需要適配器才能加載內(nèi)容,所以這里創(chuàng)建一個(gè)適配器,每一頁的內(nèi)容都是一個(gè)TextView。

public class ViewPagerAdapter extends PagerAdapter {

    private Context mContext;
    private List<String> mData;

    public ViewPagerAdapter(Context context, List<String> data) {
        mContext = context;
        mData = data;
    }

    @Override
    public int getCount() {
        return mData == null ? 0 : mData.size();
    }

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

    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);

        TextView textView = new TextView(mContext);
        textView.setLayoutParams(layoutParams);
        textView.setTextColor(Color.BLACK);
        textView.setTextSize(50);
        textView.setText(mData.get(position));
        textView.setGravity(Gravity.CENTER);
        container.addView(textView);

        return textView;
    }

    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        container.removeView((View) object);
    }
}
3.創(chuàng)建Activity

新建Activity,重寫onCreate函數(shù),調(diào)用setContentView指定布局,初始化View并調(diào)用ViewPager的addOnPageChangeListener設(shè)置頁數(shù)改變監(jiān)聽器。

public class TrackActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener {

    private ViewPager mViewPager;
    private TrackView mTrackView;

    private ViewPagerAdapter mViewPagerAdapter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_track);
        initView();
    }

    private void initView() {
        mViewPager = findViewById(R.id.view_pager);
        mTrackView = findViewById(R.id.view_track);

        List<String> data = new ArrayList<>();
        for (int i = 0; i < 5; i ++) {
            data.add(String.format("當(dāng)前頁數(shù):%s", i + 1));
        }
        mViewPagerAdapter = new ViewPagerAdapter(this, data);
        mViewPager.setAdapter(mViewPagerAdapter);
        mViewPager.addOnPageChangeListener(this);
    }

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

    }

    @Override
    public void onPageSelected(int position) {
        // 該函數(shù)為頁數(shù)改變回調(diào),通過該回調(diào)更新指示器
        mTrackView.updateByPage(position, mViewPagerAdapter.getCount());
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }
}

運(yùn)行效果如下:

運(yùn)行效果.png

最后

如果這個(gè)功能讓我做,強(qiáng)烈要求使用類似ScrollView那樣的效果實(shí)現(xiàn),這樣指示器相當(dāng)于ScrollBar,類似淘寶那樣的效果,用戶體驗(yàn)很好,這篇文章就不演示這種實(shí)現(xiàn)方式了,實(shí)現(xiàn)起來也不是很難,留給有興趣的同學(xué)們搞。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,694評論 1 32
  • 今天是我公司英國合伙人出差來中國韓國日本的第十三天、也就是意味著我2周沒有休息過了 ,周末時(shí)間也是在拜訪工作的客戶...
    極簡如蟬閱讀 676評論 5 1
  • 周日下午在西西弗,剛好有一本書的時(shí)間,放棄了《外婆的道歉信》和《聽楊絳談往事》,拿起《自控力:和壓力做朋友》如此方...
    謝小蔥啊閱讀 1,260評論 0 3
  • 從見到你的那一刻起,我就知道你我相伴的日子最多不過十五年,我知道你終將離開,可我卻無法從容地跟你說聲再見,因?yàn)槲矣?..
    十落莫玖閱讀 191評論 0 1
  • 院子里種滿了菩提樹,陽光通過葉間縫隙撒下來,斑駁陸離。 林意向前走了幾步,到了大殿外,那里有一個(gè)很大的香爐...
    楊阿雪Leo閱讀 168評論 0 2

友情鏈接更多精彩內(nèi)容