自定義View — 頁(yè)面指示器

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

寫在前面

最近項(xiàng)目又改了UI,真是一件開(kāi)心的事情(微笑臉)静稻,效果圖見(jiàn)圖一,右上角有一個(gè)水平的白色線條匈辱,還有一個(gè)灰色的背景線條振湾,就是這個(gè)東西,它是一個(gè)指示器亡脸。起初在沒(méi)看到代碼之前押搪,我以為添加應(yīng)用的界面類似ScrollView這種東西做的,如圖二的淘寶首頁(yè)輪播圖下面部分浅碾,指示器和內(nèi)容聯(lián)動(dòng)大州,手指滑動(dòng)內(nèi)容,指示器就會(huì)實(shí)時(shí)隨之變化垂谢,具體效果詳見(jiàn)淘寶首頁(yè)厦画。但是這里面有一個(gè)問(wèn)題,我們項(xiàng)目這個(gè)界面用ViewPager寫的埂陆,所以解決方案是根據(jù)頁(yè)數(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開(kāi)始距離
    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è)置畫筆開(kāi)始和結(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);
    }

    /**
     * 繪制前景線條,會(huì)動(dòng)的那條線民鼓,根據(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) {
            // 如果測(cè)量模式為未知或wrap_content薇芝,則返回默認(rèn)值。
            case MeasureSpec.UNSPECIFIED:
            case MeasureSpec.AT_MOST:
                result = size;
                break;
            // 如果測(cè)量模式為具體數(shù)值或match_parent丰嘉,則返回具體數(shù)值夯到。
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            default:
                break;
        }
        return result;
    }

    /**
     * 根據(jù)頁(yè)數(shù)更新指示器位置
     * @param position 當(dāng)前頁(yè)數(shù)
     * @param position 總頁(yè)數(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>

如何使用

下面通過(guò)一個(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è)適配器,每一頁(yè)的內(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è)置頁(yè)數(shù)改變監(jiān)聽(tīng)器能扒。

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)前頁(yè)數(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ù)為頁(yè)數(shù)改變回調(diào),通過(guò)該回調(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)起來(lái)也不是很難灵迫,留給有興趣的同學(xué)們搞秦叛。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瀑粥,隨后出現(xiàn)的幾起案子挣跋,更是在濱河造成了極大的恐慌,老刑警劉巖狞换,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件避咆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡修噪,警方通過(guò)查閱死者的電腦和手機(jī)查库,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)黄琼,“玉大人樊销,你說(shuō)我怎么就攤上這事。” “怎么了围苫?”我有些...
    開(kāi)封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵裤园,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我剂府,道長(zhǎng)拧揽,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任腺占,我火速辦了婚禮淤袜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘湾笛。我一直安慰自己饮怯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布嚎研。 她就那樣靜靜地躺著蓖墅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪临扮。 梳的紋絲不亂的頭發(fā)上论矾,一...
    開(kāi)封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音杆勇,去河邊找鬼贪壳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蚜退,可吹牛的內(nèi)容都是我干的闰靴。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼钻注,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蚂且!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起幅恋,我...
    開(kāi)封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤杏死,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后捆交,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體淑翼,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年品追,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了玄括。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肉瓦,死狀恐怖惠豺,靈堂內(nèi)的尸體忽然破棺而出银还,到底是詐尸還是另有隱情风宁,我是刑警寧澤洁墙,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站戒财,受9級(jí)特大地震影響热监,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜饮寞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一孝扛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧幽崩,春花似錦苦始、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蹄溉,卻和暖如春咨油,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背柒爵。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工役电, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人棉胀。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓法瑟,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親唁奢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子霎挟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

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