<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Android實現(xiàn)炫酷的上下翻動切換效果

          共 10156字,需瀏覽 21分鐘

           ·

          2021-06-11 17:47

          需求描述


          現(xiàn)在市面上主流的app的主界面,都是底下一排切換按鈕,上面顯示不同的界面。對于一些功能比較少的app,又想吸引人的app,咋辦呢?那當然就是功能不夠,效果來湊~


          一般的RadioButton都是像這樣,圖片很文字同時顯示,選中的高亮操作:



          這不夠風騷,我們來做一個圖片和文字滾動切換,效果炫酷的RadioButton:



          看到這里,雖然也沒有很炫酷,但至少比單調(diào)的高亮要強那么一點吧~


          功能實現(xiàn)


          選擇控件


          當業(yè)務(wù)丟給你這么一個效果讓你實現(xiàn),我們首先要挑選合適的控件,在這里,很顯然選用RadioGroup會有很大優(yōu)勢。因為我們只需要自定義RadioButton,點擊其他按鈕更換選中和非選中效果時,通過重寫setChecked(boolean checked)方法,就能夠輔助我們實現(xiàn)各自的操作和狀態(tài)。


          效果拆解


          這里涉及到幾個效果:


          • 文字和圖片的切換
            默認顯示圖片,選中時顯示文字。有個上下翻動的效果,其實這里我們只需要把圖片和文字豎著排列,用圖展示就是這樣:



          • 當選中的時候,把整體往上移動,將文字顯示在視野中,圖片被移動到上邊界之外。


          • 底部的小點切換
            底部圓點只需要選中的時候畫出來,未選中啥都不畫就行了。


          • 遮擋層的繪制
            有的朋友可能會問,什么遮擋層,這里那里有遮擋嗎,答案是有的。如果只是簡單的進行上下移動操作,是不需要用到這個的,但是仔細觀察效果好像是斜著上下切換的,這邊用了一個視覺欺騙,我把相關(guān)部分染色一下,瞬間就能看出貓膩了:



          可以很清楚的看到有兩條斜杠擋在了前面,由于和底色相同,在視覺上給了我們一種斜著切換的效果。


          代碼實現(xiàn)


          具體的實現(xiàn)方案已經(jīng)分析完畢,剩下的就是通過代碼實現(xiàn)這個控件了~


          • 首先在attr.xml中,定義一些可以自定義的屬性:

          <declare-styleable name="FantasticRadioButton" tools:ignore="ResourceName">    <!-- 圖片的引用 -->    <attr name="fantastic_drawable" format="reference" />    <!-- 底部小點的顏色 -->    <attr name="bottom_dot_color" format="color" />    <!-- 圖片的高度 -->    <attr name="icon_width" format="dimension" />    <!-- 圖片的寬度 -->    <attr name="icon_height" format="dimension" />    <!-- 底部小點的寬度 -->    <attr name="bottom_dot_width" format="dimension" />    <!-- 文字內(nèi)容 -->    <attr name="label" format="string" />    <!-- 文字大小 -->    <attr name="label_size" format="dimension" />    <!-- 文字顏色 -->    <attr name="label_color" format="color" />    <!-- 遮罩的顏色,設(shè)置成和底色一致 -->    <attr name="bg_color" format="color" /></declare-styleable>


          • 繼承AppCompatRadioButton進行自定義,獲取一些在xml中定義的屬性:


          public FantasticRadioButton(Context context, AttributeSet attrs) {    super(context, attrs);    initialize(context, attrs, 0);}
          private void initialize(Context context, AttributeSet attrs, int defStyleAttr) { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FantasticRadioButton);
          mBgColor = a.getColor(R.styleable.FantasticRadioButton_bg_color, Color.WHITE); mBottomDotColor = a.getColor(R.styleable.FantasticRadioButton_bottom_dot_color, Color.parseColor("#EE82EE")); mBottomStaticRectWidth = a.getDimensionPixelOffset(R.styleable.FantasticRadioButton_bottom_dot_width, SysUtils.convertDpToPixel(4)); mDrawableWidth = a.getDimensionPixelOffset(R.styleable.FantasticRadioButton_icon_width, 0); mDrawableHeight = a.getDimensionPixelOffset(R.styleable.FantasticRadioButton_icon_height, 0); mTextStr = SysUtils.getSafeString(a.getString(R.styleable.FantasticRadioButton_label)); mTextColor = a.getColor(R.styleable.FantasticRadioButton_label_color, Color.GREEN); mTextSize = a.getDimensionPixelOffset(R.styleable.FantasticRadioButton_label_size, SysUtils.convertDpToPixel(16));
          Drawable drawable = a.getDrawable(R.styleable.FantasticRadioButton_fantastic_drawable); if (drawable != null) { mIconBitmap = drawableToBitmap(drawable); }
          a.recycle();
          mTargetDistances = mDrawableHeight * 3f;
          setButtonDrawable(null);
          mPaint.setAntiAlias(true); mPaint.setStrokeWidth(5f);
          mCirclePaint.setAntiAlias(true); mCirclePaint.setColor(mBottomDotColor); mCirclePaint.setStrokeWidth(5f); mCirclePaint.setStyle(Paint.Style.FILL);}


          這里的mTargetDistances是指的圖文需要移動的總距離,這里設(shè)置為圖片高度的三倍。mCirclePaint是專門繪制底部小圓點的畫筆。


          • 設(shè)置寬高

          @Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {    super.onSizeChanged(w, h, oldw, oldh);    if (w > 0 && h > 0) {        mWidth = w - mInnerPaddingX * 2;        mHeight = h - mInnerPaddingY * 2;
          // 確定畫布的中心點 mCenterX = w / 2;        mCenterY = h / 2; }}


          在onSizeChanged回調(diào)中,這時候視圖的寬高已經(jīng)確定,因為在這里我們給寬高和中心點的x和y進行賦值。


          • 繪制圖片和文字

          private void drawIconAndText(Canvas canvas) {    canvas.save();    int left = mCenterX - mIconBitmap.getWidth() / 2;    int top = mCenterY - mIconBitmap.getHeight() / 2;    canvas.drawBitmap(mIconBitmap, left, top - mTransDistances, mPaint);
          // 畫字 float scaledSizeInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, mTextSize, getResources().getDisplayMetrics()); mTextPaint.setAntiAlias(true); mTextPaint.setStyle(Paint.Style.FILL); mTextPaint.setTextSize(scaledSizeInPixels); mTextPaint.setTextAlign(Paint.Align.CENTER); mTextPaint.setFakeBoldText(true); mTextPaint.setColor(mTextColor); Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt(); float baseline = (mHeight + mTargetDistances - mTransDistances - fontMetrics.bottom - fontMetrics.top) / 2; canvas.drawText(mTextStr, mCenterX, baseline, mTextPaint); canvas.restore();}


          就是很基礎(chǔ)的繪制一個bitmap和text,需要注意的是,這里有一個變量:mTransDistances,這是一個很關(guān)鍵的變量,他代表的是當前移動的距離,我們就通過改變這個變量,來實現(xiàn)移動的效果!


          • 繪制底部小點

          private void drawBottomDot(Canvas canvas) {    canvas.save();    canvas.drawRoundRect(new RectF(mCenterX - mBottomRectWidth / 2, mHeight - mBottomRectHeight - ONE_DP, mCenterX + mBottomRectWidth / 2, mHeight - ONE_DP),        mBottomRectHeight / 2, mBottomRectHeight / 2, mCirclePaint);
          canvas.restore();}


          這里的mButtomRectWidth和mBottomHeight也是變量,通過改變這兩個值,來實現(xiàn)縮放的效果。


          • 繪制遮罩層

          private void drawLayerPath(Canvas canvas) {    canvas.save();    float upTransDistance = -mLayerHeight / 2;    Path path = new Path();    path.moveTo(mCenterX - mWidth / 3, upTransDistance);    path.lineTo(mCenterX + mWidth / 3, upTransDistance + SysUtils.convertDpToPixel(10));    path.lineTo(mCenterX + mWidth / 3, upTransDistance + SysUtils.convertDpToPixel(10) + mLayerHeight);    path.lineTo(mCenterX - mWidth / 3, upTransDistance + mLayerHeight);    path.lineTo(mCenterX - mWidth / 3, upTransDistance);    path.close();    mPaint.setColor(mBgColor);    mPaint.setStyle(Paint.Style.FILL);    canvas.drawPath(path, mPaint);    Path path2 = new Path();    float startY = upTransDistance + mLayerHeight * 2;    path2.moveTo(mCenterX - mWidth / 3, startY);    path2.lineTo(mCenterX + mWidth / 3, startY + SysUtils.convertDpToPixel(10));    path2.lineTo(mCenterX + mWidth / 3, startY + SysUtils.convertDpToPixel(10) + mLayerHeight);    path2.lineTo(mCenterX - mWidth / 3, startY + mLayerHeight);    path2.lineTo(mCenterX - mWidth / 3, startY);    path2.close();    canvas.drawPath(path2, mPaint);    canvas.restore();}


          這里沒啥特別的,就是繪制兩條斜杠在畫布上。


          • 過渡動畫制作
            這里采用屬性動畫,通過改變上面的變量,然后postInvalidate調(diào)用onDraw()來重繪界面實現(xiàn)過渡效果:

          private ValueAnimator startBottomLineAnimation() {        ValueAnimator lineAnimation = ValueAnimator.ofFloat(0f, mBottomStaticRectWidth);        lineAnimation.setDuration(DURATION_TIME);        lineAnimation.setInterpolator(new TimeInterpolator() {            @Override            public float getInterpolation(float v) {                return 1 - (1 - v) * (1 - v);            }        });        lineAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                mBottomRectWidth = (float) animation.getAnimatedValue();                // 高度是寬度的五分之一                mBottomRectHeight = mBottomRectWidth / 5;                postInvalidate();            }        });        return lineAnimation;    }


          這是底部小圓點的動畫,重寫插播器來控制動畫的速度,先快后慢,關(guān)于動畫插播器的知識,百度有很多資料~這里通過屬性動畫控制小圓點的高度和寬度。

          private ValueAnimator getIconAndTextAnimation() {    ValueAnimator transAnimation = ValueAnimator.ofFloat(0f, mTargetDistances);    transAnimation.setDuration(DURATION_TIME);    transAnimation.setInterpolator(new TimeInterpolator() {        @Override        public float getInterpolation(float v) {            return 1 - (1 - v);        }    });    transAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {        @Override        public void onAnimationUpdate(ValueAnimator animation) {            mTransDistances = (float) animation.getAnimatedValue();            postInvalidate();        }    });    transAnimation.addListener(new AnimatorListenerAdapter() {        @Override        public void onAnimationCancel(Animator animation) {            mIsTransEnd = true;            postInvalidate();        }
          @Override public void onAnimationEnd(Animator animation) { mIsTransEnd = true; postInvalidate(); }
          @Override public void onAnimationStart(Animator animation) { mIsTransEnd = false; postInvalidate(); }
          @Override public void onAnimationPause(Animator animation) { mIsTransEnd = true; postInvalidate(); } }); return transAnimation;}


          這里是改變圖文平移距離的屬性動畫控制器,并對動畫狀態(tài)進行監(jiān)聽,只有在動畫執(zhí)行過程中,也就是切換的時候,才繪制遮擋層。


          • 繪制
            準備工作都做完了,只差最后一步繪制了!

          protected void onDraw(Canvas canvas) {    super.onDraw(canvas);    if (mWidth <= 0 || mHeight <= 0 || mIconBitmap == null || mIconBitmap.isRecycled()) {        return;    }
          // 先畫圖片和文字切換的部分 if (mIconBitmap != null) { drawIconAndText(canvas); } // 畫一個遮擋層,為了遮住放在圖片下面的文字 float layerHeight = (getHeight() - mIconBitmap.getHeight()) / 3; mPaint.setColor(mBgColor); canvas.drawRect(0, getHeight() - layerHeight, mWidth, getHeight(), mPaint);
          // 最后畫遮擋條 if (!mIsTransEnd) { drawLayerPath(canvas); }
          // 最后最后畫底部圓圈 drawBottomDot(canvas);}


          繪制的時候只需要注意繪制順序就行了,因為他是一層層往上繪制的。


          • 最后一步
            至此,一個自定義RadioButton算是完成了,最后只需要重寫setChecked(boolean checked)函數(shù),根據(jù)是否選中來執(zhí)行對應(yīng)的動畫,就大功告成了:

          @Overridepublic void setChecked(boolean checked) {    boolean isChanged = checked != isChecked();
          super.setChecked(checked);
          // ValueAnimator bottomCircleAni = getCircleAnimation(); ValueAnimator bottomLineAni = startBottomLineAnimation(); ValueAnimator iconAni = getIconAndTextAnimation(); if (isChanged) { if (checked) {// startCircleAnimator(); bottomLineAni.start(); iconAni.start(); postInvalidate(); } else { bottomLineAni.reverse(); iconAni.reverse(); postInvalidate(); } }}


          使用


          使用方法很簡單,直接用原生RadioGroup包裹我們自定義的RadioButton就行了,然后把RadioButton設(shè)置一下屬性即可:



          總結(jié)


          自定義樣式的RadioButton多種多樣,這里也是給大家提供一種樣式,當然,重要的是解決問的思路和方法,辦法總比困難多,不斷試錯,找到正確的道路,那么距離解決問題也不會遠了!


          源碼地址:

          https://github.com/wx9265661/SmallDemos2


          到這里就結(jié)束啦。

          瀏覽 132
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美成人精品免费 | 免费成人欧美 | 91中文字幕在线 | 爱爱的免费视频 | 人妻无码中文字幕蜜桃 |