仿VPGAME客戶端跟RecyclerView聯(lián)動(dòng)指針控件

先看VPGAME客戶端的這個(gè)效果:

2017-08-28-10mzvp.gif

接著是我實(shí)現(xiàn)的效果:

2017-08-28-10mzdemo.gif

轉(zhuǎn)成gif圖質(zhì)量不太好,實(shí)際效果比這個(gè)好很多,可以去運(yùn)行demo看看實(shí)際效果卵惦。鏈接:https://github.com/DarkSherlock/DateViewWithRvDemo

我們可以看到這個(gè)效果,當(dāng)recyclerview滑動(dòng)的時(shí)候玖像,這個(gè)控件里的那個(gè)時(shí)鐘指針
會(huì)跟著轉(zhuǎn)動(dòng)樱溉,后面的文字也會(huì)跟著item的值 有一個(gè)滑進(jìn)滑出動(dòng)畫挣输。

我本以為這是一個(gè)自定義View,然而當(dāng)我用打開DDMS用HierachyView查看它的布局的時(shí)候。

VPGAME布局分析

我們可以看到他這個(gè)不是用一個(gè)自定義View來完成的饺窿,而是多個(gè)自定義View
來組合在RelativieLayout里來實(shí)現(xiàn)的歧焦。那么我們就可以借鑒他的這個(gè)思路。

Studio打開HierachyView的步驟:

ddms.png
dump.png

那么接下來就來分析下實(shí)現(xiàn)的思路:

1.首先要和RecyclerView完成交互肚医,那么就需要添加OnScrollListener來監(jiān)聽
RV的滑動(dòng)绢馍,根據(jù)滑動(dòng)距離來算出滑動(dòng)了幾個(gè)Item,根據(jù)Item的某字段(它這里是時(shí)間月份)來傳給自定義控件肠套,讓其完成UI更新舰涌。
2.那個(gè)滑進(jìn)滑出的控件,覺得不需要再去自定義你稚,只需要用TextView加位移動(dòng)畫就能實(shí)現(xiàn)瓷耙。
3.自定義指針轉(zhuǎn)動(dòng)控件,根據(jù)OnScrollListener監(jiān)聽到的dy滑動(dòng)距離刁赖,來設(shè)置轉(zhuǎn)動(dòng)的角度搁痛。

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

  //為了和dateview 完成聯(lián)動(dòng),添加滑動(dòng)監(jiān)聽
  rv.addOnScrollListener(new MyScrollListener());

在onScrollListener()里著重關(guān)注onScrolled()宇弛;


        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            if ( mRvItemHeight != 0 ) {
                y += dy;
                //將累計(jì)的滑動(dòng)距離 跟一個(gè)item的高度 比較鸡典,判斷滑動(dòng)了相當(dāng)于幾個(gè)item的距離。
                float position = y / mRvItemHeight;

                //將每次滑動(dòng)了相當(dāng)于多少個(gè)Item高度的值傳給指針控件枪芒,
                //滑動(dòng)一個(gè)item高度指針就轉(zhuǎn)動(dòng)一圈彻况,按比例轉(zhuǎn)動(dòng)角度。
                dateview.setProcess(position);

                mBean = mList.get((int) position);//拿到對(duì)應(yīng)的item的javabean

                //只要有輕微的滑動(dòng)onScrolled就會(huì)調(diào)用舅踪,但是我們不需要這么頻繁的去更新滑進(jìn)滑出的UI
                //所以我們這里判斷只有當(dāng)2個(gè)item的月份字段不一樣的時(shí)候纽甘,這時(shí)候需要執(zhí)行滑進(jìn)滑出的
                //動(dòng)畫,并且將月份更新顯示抽碌。
                if (mBean.getMonth()!= Integer.parseInt(mTvMonth.getText().toString())) {
                    mCurrentMonth = mBean.getMonth();
                    if (dy > 0) { //判斷執(zhí)行向上還是向下滑動(dòng)動(dòng)畫
                        startUpAnim( );
                    } else {
                        startDownAnim();
                    }

                }
            }
        }

接著看看動(dòng)畫:
由于位移動(dòng)畫我們需要拿到執(zhí)行動(dòng)畫的textview的Y軸起始坐標(biāo)和高度悍赢,所以我們post一個(gè)runnable(直接在activity的oncreat()中去拿的話因?yàn)榭丶赡苓€未layout完畢,所以可能取到的值為0)货徙;
動(dòng)畫分為:1.向上滑出動(dòng)畫2.向上滑進(jìn)動(dòng)畫3.向下滑出動(dòng)畫4.向下滑進(jìn)動(dòng)畫泽裳。
textview向上滑出頂部不可見后再?gòu)牡撞肯蛏匣M(jìn)(1執(zhí)行完畢后執(zhí)行2)
textview向下滑出底部不可見后再?gòu)捻敳肯蛳禄M(jìn)(3執(zhí)行完畢后執(zhí)行4)

        //post 一個(gè)runnable 待 view layout 完畢后測(cè)量 rcyclerview item的高度 并且初始化動(dòng)畫
        rv.post(new Runnable() {
            @Override
            public void run() {
                View childAt = rv.getLayoutManager().findViewByPosition(0);
                if (childAt != null) {
                    mRvItemHeight = (float) childAt.getHeight();
                    initAnimation();
                }
            }
        });
private void initAnimation() {
    // Y軸方向上的坐標(biāo)
    float translationY = mTvMonth.getTranslationY();
    float tvMonthHeight = mTvMonth.getHeight();
    //向上彈出動(dòng)畫
    //第一個(gè)參數(shù)是要執(zhí)行動(dòng)畫的控件,第二個(gè)參數(shù)是更改的屬性字段(需帶有setter方法)破婆,
    //第三個(gè)參數(shù)是 動(dòng)畫開始時(shí) 要更改的屬性字段的起始值,第四個(gè)是結(jié)束時(shí)的值(translationY - tvMonthHeight 相當(dāng)于滑出邊界不可見了胸囱。)
    //這里指mTvMonth執(zhí)行Y軸上的坐標(biāo) 更改(Y軸位移動(dòng)畫)
    mUpAnimOut = ObjectAnimator.ofFloat(mTvMonth, "translationY", translationY, translationY - tvMonthHeight);
    //向上彈進(jìn)動(dòng)畫
    mUpAnimIn =ObjectAnimator.ofFloat(mTvMonth, "translationY", translationY + tvMonthHeight, translationY);
    mUpAnimOut.setDuration(ANIMATION_DURATION);
    mUpAnimIn.setDuration(ANIMATION_DURATION);
    //添加動(dòng)畫執(zhí)行監(jiān)聽
    addUpAnimListener(mUpAnimIn);

    //向下彈出動(dòng)畫
    mDownAnimOut =ObjectAnimator.ofFloat(mTvMonth, "translationY", translationY, translationY + tvMonthHeight);
    //向下彈進(jìn)動(dòng)畫
    ObjectAnimator downAnimIn =ObjectAnimator.ofFloat(mTvMonth, "translationY", translationY - tvMonthHeight, translationY);

    mDownAnimOut.setDuration(ANIMATION_DURATION);
    downAnimIn.setDuration(ANIMATION_DURATION);
    //添加動(dòng)畫執(zhí)行監(jiān)聽
    addDownAnimListener(downAnimIn);
}
private void addUpAnimListener(final ObjectAnimator upAnimIn) {
        mUpAnimOut.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (!upAnimIn.isStarted()) {
                    upAnimIn.start();
                }
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });

        upAnimIn.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                mTvMonth.setText(String.valueOf(mCurrentMonth));
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                //當(dāng)recycler滑動(dòng)速度非车灰ǎ快的時(shí)候,當(dāng)前的動(dòng)畫還未執(zhí)行,已經(jīng)滑動(dòng)到下條數(shù)據(jù)要執(zhí)行下一個(gè)動(dòng)畫時(shí)裳扯,
                //因?yàn)槲覀兣袛嗔?upAnimIn.isStarted() ,所以下個(gè)動(dòng)畫不會(huì)執(zhí)行抛丽,這時(shí)候就需要以下判斷當(dāng)RecyclerView
                //滑動(dòng)停止,當(dāng)前動(dòng)畫結(jié)束時(shí)將正確的(下一條的數(shù)據(jù))設(shè)置給mTvMonth饰豺,避免數(shù)據(jù)錯(cuò)亂.
                if (scrollState == RecyclerView.SCROLL_STATE_IDLE && mCurrentMonth != Integer.parseInt(mTvMonth.getText().toString())) {
                    mTvMonth.setText(String.valueOf(mCurrentMonth));
                }
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
    }

動(dòng)畫設(shè)置執(zhí)行時(shí)間為50ms亿鲜,但是由于recyclerview可能會(huì)非常快速地滑動(dòng)冤吨,所以如果動(dòng)畫還在執(zhí)行就跳過蒿柳,在 RecyclerView滑動(dòng)停止時(shí)即狀態(tài)等于SCROLL_STATE_IDLE時(shí)將要更新的值保存下來,在動(dòng)畫執(zhí)行完畢的時(shí)候去判斷 如果數(shù)據(jù)顯示不正確再重新賦值正確的數(shù)據(jù)給textview

  /**
     * 開始向上滑出的動(dòng)畫
     */
    private void startUpAnim(  ) {
        if (!mUpAnimOut.isStarted()) {
            mUpAnimOut.start();
        }
    }
 @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            if (mBean != null) {
                scrollState = newState;
                //當(dāng)非充鲶。快速滑動(dòng)的時(shí)候 在滑動(dòng)的最后判斷數(shù)據(jù)是否準(zhǔn)確垒探,將正確的數(shù)據(jù)返回。
                if (scrollState == RecyclerView.SCROLL_STATE_IDLE && mCurrentMonth != Integer.parseInt(mTvMonth.getText().toString())) {
                    mCurrentMonth = mBean.getMonth();
                }
            }
        }

這樣動(dòng)畫的部分就實(shí)現(xiàn)完了怠李,接著看轉(zhuǎn)動(dòng)指針的部分

轉(zhuǎn)動(dòng)指針自定義View分為2部分:1.不動(dòng)的圓形背景類似于時(shí)鐘背景
2.轉(zhuǎn)動(dòng)的指針圾叼,類似于時(shí)鐘指針。
背景直接canvas.drawCircle就行捺癞,沒什么可說的夷蚊。
指針轉(zhuǎn)動(dòng)的角度就需要根據(jù)傳onScrollListener傳進(jìn)來的值進(jìn)行一定的計(jì)算來算出需要轉(zhuǎn)動(dòng)多少角度,直接看代碼就懂了。

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int height = getHeight();
        int width = getWidth();
        int radius = width / 2;//圓形背景半徑
        canvas.translate(width / 2, height / 2);

        canvas.save();
        //畫灰色圓形背景
        canvas.drawCircle(0, 0, width / 2, mCirclePaint);

        //畫12 3 6 9 四個(gè)刻度   長(zhǎng)度為半徑(width/2)的0.25
        mCursor.setColor(Color.parseColor("#FFAAAAAA"));

        canvas.drawLine(0, -height / 2, 0, ((radius * R_QUARTER) - height / 2), mCursor);//12
        canvas.drawLine(width / 2, 0, (width / 2 - (radius * R_QUARTER)), 0, mCursor);//3
        canvas.drawLine(0, height / 2, 0, (height / 2 - (radius * R_QUARTER)), mCursor);//6
        canvas.drawLine(-width / 2, 0, (-width / 2 + (radius * R_QUARTER)), 0, mCursor);//9

        //畫根據(jù)傳進(jìn)來的process 轉(zhuǎn)動(dòng)的指針
        int stopX = (int) (0.6 * (width / 2) * Math.sin(mProcess * 2 * Math.PI));
        int stopY = (int) (0.6 * (width / 2) * Math.cos(mProcess * 2 * Math.PI));
        mCursor.setColor(Color.WHITE);
        canvas.drawLine(0, 0, stopX, -stopY, mCursor);
    }
/**
     * 設(shè)置指針轉(zhuǎn)動(dòng)角度比率
     * @param process
     */
    public void setProcess(float process) {
        this.mProcess = process;
        invalidate();
    }

這樣就完成了髓介,挺簡(jiǎn)單的代碼惕鼓,完整的代碼可以去githup上的demo中看。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末版保,一起剝皮案震驚了整個(gè)濱河市呜笑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌彻犁,老刑警劉巖叫胁,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異汞幢,居然都是意外死亡驼鹅,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門森篷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來输钩,“玉大人,你說我怎么就攤上這事仲智÷蚰耍” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵钓辆,是天一觀的道長(zhǎng)剪验。 經(jīng)常有香客問我肴焊,道長(zhǎng),這世上最難降的妖魔是什么功戚? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任娶眷,我火速辦了婚禮,結(jié)果婚禮上啸臀,老公的妹妹穿的比我還像新娘届宠。我一直安慰自己,他們只是感情好乘粒,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布豌注。 她就那樣靜靜地躺著,像睡著了一般谓厘。 火紅的嫁衣襯著肌膚如雪幌羞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天竟稳,我揣著相機(jī)與錄音属桦,去河邊找鬼。 笑死他爸,一個(gè)胖子當(dāng)著我的面吹牛聂宾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播诊笤,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼系谐,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了讨跟?” 一聲冷哼從身側(cè)響起纪他,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎晾匠,沒想到半個(gè)月后茶袒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凉馆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年薪寓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片澜共。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡向叉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嗦董,到底是詐尸還是另有隱情母谎,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布京革,位于F島的核電站销睁,受9級(jí)特大地震影響供璧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜冻记,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望来惧。 院中可真熱鬧冗栗,春花似錦、人聲如沸供搀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)葛虐。三九已至胎源,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間屿脐,已是汗流浹背涕蚤。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留的诵,地道東北人万栅。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像西疤,于是被迫代替她去往敵國(guó)和親烦粒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,527評(píng)論 25 707
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,712評(píng)論 22 664
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)代赁、插件扰她、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,033評(píng)論 4 62
  • 20170828-0903號(hào) 本周計(jì)劃 5理想的狀況 a制度改革了, 效益提高了 b學(xué)習(xí)升級(jí)了 幸福指數(shù)高 c游戲...
    芮涵琪雪閱讀 126評(píng)論 0 0
  • 忘了怎樣的開始 忘了怎樣的結(jié)束 沒有你的日子 總是下著漫天的雨 走不出從前 走不出回憶 無盡的情思 總是纏繞在過去...
    小金甲閱讀 150評(píng)論 0 0