亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android自定義View實(shí)現(xiàn)帶音效和震動(dòng)的SeekBar

 更新時(shí)間:2024年03月24日 10:33:53   作者:職場007  
這篇文章主要為大家詳細(xì)介紹了Android如何自定義View實(shí)一個(gè)帶音效和震動(dòng)的SeekBar,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

需求描述

當(dāng)我們需要做一些帶校準(zhǔn)的功能時(shí),需要調(diào)節(jié)一些值來反映校準(zhǔn)的效果,或者是相機(jī)之類的應(yīng)用,需要設(shè)置焦距,曝光值之類的,為了方便用戶設(shè)置這些值,通常需要用到滑動(dòng)選擇的控件,比如系統(tǒng)提供的SeekBar控件。用戶通過滑動(dòng)屏幕就能設(shè)置值。使用系統(tǒng)的seekBar雖然可以完成這些功能,但是不美觀。一般產(chǎn)品都不會(huì)采納系統(tǒng)的原生控件,所以只能是我們自己來通過自定義view繪制。今天我們要繪制的自定義View如下所示:

然后在第一次的時(shí)候,會(huì)有個(gè)動(dòng)畫提示用戶,如何操作。效果如下:

最后用戶開始操作動(dòng)畫就會(huì)消失,用戶操作時(shí)的效果如下:

本文就是主要介紹如何實(shí)現(xiàn)這樣一個(gè)控件,這個(gè)控件在滑動(dòng)的時(shí)候會(huì)伴隨音效以及手機(jī)的震動(dòng)感。

思路

當(dāng)我們拿到一個(gè)自定義View控件需求的時(shí)候,首先我們需要先分析下這個(gè)自定義控件是否可以使用系統(tǒng)已經(jīng)有的控件組合實(shí)現(xiàn),如果不能,我們?cè)俜治鲞@個(gè)自定義控件是一個(gè)view還是可以放子view的容器(ViewGroup)。如果是一個(gè)容器類的自定義控件,我們就需要繼承自ViewGroup。否則就需要我們繼承自View自己繪制,然后再添加對(duì)應(yīng)的事件處理就行了。本文要實(shí)現(xiàn)的自定義控件屬于需要繼承自View自己繪制的。首先我們要繪制的View,為了方便我們稱為RulerSeekBar。這個(gè)RulerSeekBar由幾部分組成,分別是:提示文本、指示的指針、長短刻度以及數(shù)字。接下來我們需要做的就是計(jì)算出他們的對(duì)應(yīng)坐標(biāo),然后使用繪圖API繪制出來就行了。繪制完View后我們需要做事件處理,比如滑動(dòng)的時(shí)候的吸附效果,慣性滑動(dòng),音效,震動(dòng)處理。而滾動(dòng)的時(shí)候我們使用的是ScrollerView。其實(shí)自定義Android中沒有的view控件就是將需要繪制的View樣式分解成基本圖形,算出每個(gè)需要繪制的基本圖形坐標(biāo),使用繪圖的API將其分別繪制就行了,然后就是處理事件和調(diào)整細(xì)節(jié)。

繪制提示文本

RulerSeekBar的提示文本是支持多色字體的,這里我們主要使用Android系統(tǒng)提供的SpannableString,這個(gè)類運(yùn)行我們定義各種樣式的文本,甚至可以放圖片,特別好用。不了解的小伙伴可以去百度下。這個(gè)類真的很炫。但是我們是繼承自View的,所以繪制SpannableString需要借助DynamicLayout的幫助。否則無法繪制出不同樣式的文本。

指示指針

指示指針包括兩部分,一個(gè)圖標(biāo),一個(gè)帶漸變的小圓矩形指針。我們算出他們的坐標(biāo)后使用繪圖API繪制出來就行了

長短刻度和數(shù)字

刻度分為長刻度和短刻度,為了不混淆,我使用的是兩個(gè)畫筆繪制分別繪制。然后每個(gè)刻度的坐標(biāo)計(jì)算,我們可以使用當(dāng)前控件的寬除以每個(gè)刻度的間隔大小就能得出當(dāng)前的寬可以繪制多少個(gè)刻度。而對(duì)于數(shù)字,我們可以根據(jù)設(shè)置的最大值和最小值,刻度間的間隔,當(dāng)前的位置等信息,計(jì)算每個(gè)刻度的數(shù)字的坐標(biāo)并繪制,這里處理的時(shí)候?qū)⒚總€(gè)刻度放大十倍處理,這樣可以防止在計(jì)算過程中精度的丟失,回調(diào)數(shù)據(jù)的時(shí)候再縮小10倍將值給到用戶

陰影效果繪制

我們仔細(xì)觀察可以發(fā)現(xiàn),當(dāng)我們的RulerSeekBar的兩邊刻度有個(gè)陰影效果,當(dāng)我們左滑或者右滑的時(shí)候,會(huì)出現(xiàn)一個(gè)漸變,給人一種漸漸消失的感覺,這種效果我們主要通過改變畫筆的透明度實(shí)現(xiàn)的。具體的看代碼

吸附效果和慣性滑動(dòng)

當(dāng)我們滑動(dòng)RulerSeekBar控件選擇數(shù)值時(shí),有時(shí)候會(huì)滑動(dòng)到兩個(gè)刻度之間,當(dāng)我們放開手的時(shí)候,控件會(huì)自動(dòng)吸附到兩個(gè)刻度中的一個(gè)。這種判斷就是當(dāng)滑動(dòng)的距離超過了一個(gè)閾值后就選擇后面的一個(gè)刻度,否則回彈回上一個(gè)刻度。而慣性滑動(dòng)就是我們所說的Fling,指的是我們?cè)谄聊簧峡焖倩瑒?dòng)然后突然停止后,由于慣性,還會(huì)滑動(dòng)一段距離,這里我們需要借助于速度跟蹤器:VelocityTracker和Scroller實(shí)現(xiàn),具體見代碼

音效震動(dòng)處理

當(dāng)滑動(dòng)的時(shí)候有個(gè)音效感覺會(huì)好很多,這時(shí)候如果能加上震動(dòng)效果就會(huì)更好,這里我們使用的是系統(tǒng)的 Vibrator實(shí)現(xiàn)震動(dòng),SoundPool實(shí)現(xiàn)音效播放。

提示動(dòng)畫的實(shí)現(xiàn)

因?yàn)閯?dòng)畫只是一個(gè)橫向反復(fù)平移。所以我們可以借助于屬性動(dòng)畫的ValueAnimator計(jì)算出值,然后調(diào)用View的invalidate()方法觸發(fā)view繪制需要?jiǎng)赢嫷膶?duì)象就行,本文中需要?jiǎng)赢嫷膶?duì)象是(小手圖標(biāo))

代碼解析

初始化

在初始化的時(shí)候我們將自定義的屬性解析出來并賦給當(dāng)前類的成員變量,并且初始化畫筆和一些值

 public RulerSeekBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 初始化自定義屬性
        initAttrs(context, attrs);
        // 滑動(dòng)的閾值,后面會(huì)通過它去判斷當(dāng)前的是操作是滑動(dòng)還是觸摸操作
        ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
        TOUCH_SLOP = viewConfiguration.getScaledTouchSlop();

       // 速度追蹤器的初始化
        MIN_FLING_VELOCITY = viewConfiguration.getScaledMinimumFlingVelocity();
        MAX_FLING_VELOCITY = viewConfiguration.getScaledMaximumFlingVelocity();
        // 將距離值轉(zhuǎn)換成數(shù)字
        convertValueToNumber();
       
        // 畫筆等成員變量的初始化
        init(context);
    }

在convertValueToNumber中我們將距離轉(zhuǎn)換成對(duì)應(yīng)的數(shù)字

 private void convertValueToNumber() {
        mMinNumber = (int) (minValue * 10);
        mMaxNumber = (int) (maxValue * 10);
        mCurrentNumber = (int) (currentValue * 10);
        mNumberUnit = (int) (gradationUnit * 10);
        mCurrentDistance = (float) (mCurrentNumber - mMinNumber) / mNumberUnit * 
        gradationGap;
        mNumberRangeDistance = (float) (mMaxNumber - mMinNumber) / mNumberUnit *
         gradationGap;

        if (mWidth != 0) {
            mWidthRangeNumber = (int) (mWidth / gradationGap * mNumberUnit);
        }

        Log.d(TAG, "convertValueToNumber: mMinNumber: " + mMinNumber + " ,mMaxNumber: "
        
                + mMaxNumber + " ,mCurrentNumber: " + mCurrentNumber + " ,mNumberUnit: " +
                +  mNumberUnit
                + " ,mCurrentDistance: " + mCurrentDistance + " ,mNumberRangeDistance: " +
                +  mNumberRangeDistance
              
                + " ,mWidthRangeNumber: " + mWidthRangeNumber);
                + 
    }

在init函數(shù)中,主要是對(duì)各種畫筆和震動(dòng)音效的成員變量的初始化工作

    private void init(Context context) {
        // 短刻度畫筆
        mShortGradationPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mShortGradationPaint.setStrokeWidth(shortLineWidth);
        mShortGradationPaint.setColor(gradationColor);
        mShortGradationPaint.setStrokeWidth(shortLineWidth);
        mShortGradationPaint.setColor(gradationColor);
        mShortGradationPaint.setStrokeCap(Paint.Cap.ROUND);

        // 長刻度畫筆
        mLongGradationPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mLongGradationPaint.setStrokeWidth(longLineWidth);
        mLongGradationPaint.setStrokeCap(Paint.Cap.ROUND);
        mLongGradationPaint.setColor(Color.parseColor("#FF4AA5FD"));

        // 指針畫筆,這里用到了LinearGradient ,主要是實(shí)現(xiàn)一種漸變效果。
        int[] colors = new int[]{0x011f8d8, 0xff0ef4cb, 0x800cf2c3};
        LinearGradient linearGradient = new LinearGradient(
                0,
                0,
                100,
                100,
                colors,
                null,
                Shader.TileMode.CLAMP
        );

        mIndicatorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mIndicatorPaint.setColor(indicatorLineColor);
        mIndicatorPaint.setStrokeWidth(indicatorLineWidth);
        mIndicatorPaint.setStrokeCap(Paint.Cap.ROUND);
        mIndicatorPaint.setShader(linearGradient);
        Bitmap originBp = BitmapFactory.decodeResource(getResources(), 
        R.drawable.indicator);
        indicatorBp = Bitmap.createScaledBitmap(originBp, dp2px(222), dp2px(6.85f), true);
        originBp.recycle();

        // 手勢(shì)圖標(biāo)畫筆
        mGestureAniPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        // 文字畫筆
        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setTextSize(textGradationSize);
        mTextPaint.setColor(textGradationColor);

        mScroller = new Scroller(context);
       // 數(shù)字畫筆
        mNumPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mNumPaint.setTextSize(textGradationSize);
        mNumPaint.setColor(textGradationColor);

        mSoundPool = new SoundPool(10,AudioManager.STREAM_MUSIC,0);
        soundId = mSoundPool.load(getContext(),R.raw.sound,1);

        // 震動(dòng)效果
        vibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
    }

控件測(cè)量

在測(cè)量階段主要是決定控件的大小,這里我們只需要處理測(cè)量模式為AT_MOST的情況下的控件的高。這種模式下不做限制會(huì)導(dǎo)致子控件的高度變得異常:

  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mWidth = calculateSize(true, widthMeasureSpec);
        mHeight = calculateSize(false, heightMeasureSpec);
        mHalfWidth = mWidth >> 1;
        if (mWidthRangeNumber == 0) {
            mWidthRangeNumber = (int) (mWidth / gradationGap * mNumberUnit);
        }
        Log.d(TAG, "onMeasure: mWidthRangeNumber: " + mWidthRangeNumber + " ,mNumberUnit:
         " + mNumberUnit);

        setMeasuredDimension(mWidth, mHeight);
    }
    private int calculateSize(boolean isWidth, int measureSpec) {
        final int mode = MeasureSpec.getMode(measureSpec);
        final int size = MeasureSpec.getSize(measureSpec);
        int realSize = size;
        if (mode == MeasureSpec.AT_MOST) {
            if (!isWidth) {
                int defaultSize = dp2px(74);
                realSize = Math.min(realSize, defaultSize);
            }
        }
        Log.d(TAG, "mode: " + mode + " ,size: " + size + " ,realSize: " + realSize);
        return realSize;
    }

控件繪制

繪制階段主要是繪制背景,然后繪制刻度和數(shù)字,最后繪制指針,然后動(dòng)畫是根據(jù)變量isPlayTipAnim來決定是否繪制的,當(dāng)用戶不點(diǎn)擊控件的時(shí)候,動(dòng)畫會(huì)一直播放,用戶點(diǎn)擊了后停止對(duì)動(dòng)畫的繪制

    @Override
    protected void onDraw(Canvas canvas) {
        // 繪制背景
        canvas.drawColor(bgColor);
        // 繪制刻度和數(shù)字
        drawGradation(canvas);
        // 繪制指針
        drawIndicator(canvas);

        // 繪制動(dòng)畫的圖標(biāo)
        if (isPlayTipAnim) {
            drawGestureAniIcon(canvas);
        }
    }

提示動(dòng)畫繪制

繪制動(dòng)畫的時(shí)候我們可以使用一個(gè)ValueAnimator屬性動(dòng)畫來確定一個(gè)動(dòng)畫的范圍,當(dāng)我們開始動(dòng)畫的時(shí)候,這個(gè)類會(huì)給們計(jì)算變化的值,我們把這個(gè)值設(shè)置成小手圖標(biāo)的X坐標(biāo),保持Y坐標(biāo)不變,然后這個(gè)值每改變一次,就觸發(fā)一次重繪,這樣就完成了提示動(dòng)畫的效果了,代碼如下所示:

   private void drawGestureAniIcon(Canvas canvas) {
        if (mGestureTipBp == null) {
            Bitmap originBp = BitmapFactory.decodeResource(getResources(), 
            R.drawable.ic_gesture_tip);
            mGestureTipBp = Bitmap.createScaledBitmap(originBp, dp2px(46), dp2px(47),
             true);
            mGestureAniTransX = mHalfWidth - (float) mGestureTipBp.getWidth() / 2 + 
            dp2px(2);
            originBp.recycle();


            valueAnimator = ValueAnimator.ofFloat(
                    mHalfWidth - 11 * gradationGap,
                    mHalfWidth + 7 * gradationGap); // 此處做動(dòng)畫的范圍。按照真實(shí)情況合理調(diào)整。
            valueAnimator.addUpdateListener(animation -> {
                mGestureAniTransX = (float) animation.getAnimatedValue();
                // Log.d(TAG, "zhongxj111: mGestureAniTransX: " + mGestureAniTransX);
                invalidate();
            });
            valueAnimator.setDuration(2000);
            valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
            valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
            valueAnimator.start();
        }

        canvas.drawBitmap(mGestureTipBp,
                mGestureAniTransX,
                stopLongGradationY - (float) mGestureTipBp.getHeight() / 2 - dp2px(15),
                mGestureAniPaint
        );
    }

漸變效果的繪制

當(dāng)繪制刻度的時(shí)候,我們需要去實(shí)現(xiàn)繪制漸變效果,就是我們的控件兩邊,如果用戶左右滑動(dòng)的時(shí)候,我們的刻度有漸變的效果,感覺好像是慢慢消失一樣,這里有的讀者可能會(huì)想到讓UI切一張透明的背景,這種方法如果控件的背景是黑色的時(shí)候可行,但是控件的背景是其他的顏色的時(shí)候就會(huì)發(fā)現(xiàn)這個(gè)透明的背景很突兀,感興趣的讀者也可以去嘗試下。我的實(shí)現(xiàn)方式是通過用戶滑動(dòng)的距離換算成透明度設(shè)置給刻度的畫筆,這樣用戶滑動(dòng)的時(shí)候,距離是在變化的,或是變大,或是變小,這時(shí)候再把這個(gè)距離映射成透明的值即可。 我們的Paint的API設(shè)置透明值是一個(gè)整型的數(shù),范圍是0~255

我們只要保證設(shè)置的值在這個(gè)區(qū)間即可。 我們滑動(dòng)的時(shí)候會(huì)得到一個(gè)刻度距離最左邊或者最右邊的距離值,這個(gè)值正好可以用于換算成顏色值,注意:如果刻度間距離設(shè)置得很大,需要重新映射,這里我默認(rèn)刻度在11dp下的,滑動(dòng)的距離剛好在0~255之間 關(guān)鍵代碼如下:

   // 給控件開始的6個(gè)刻度做漸變效果
            if (distance < 6 * gradationGap) {
                Log.d(TAG, "distance==>" + distance + " ,curPosIndex=>" + curPosIndex +
                        " ,perUnitCount: " + perUnitCount + " ,factor: " + factor
                        + " ,6*gradationGap: " + 6 * gradationGap);
                //計(jì)算開始部分的透明值
                int startAlpha = Math.abs((int) (distance));
                mLongGradationPaint.setAlpha(startAlpha);
                mShortGradationPaint.setAlpha(startAlpha);
                mNumPaint.setAlpha(startAlpha);
                // 給控件的結(jié)尾做漸變效果
            } else if (distance > mWidth - 6 * gradationGap) {
                // 計(jì)算結(jié)束的透明值
                int endAlpha = Math.abs((int) ((mWidth + gradationGap) - distance));
                // Log.d(TAG, "zhongxj: endAlpha: " + endAlpha);
                mLongGradationPaint.setAlpha(endAlpha);
                mShortGradationPaint.setAlpha(endAlpha);
                mNumPaint.setAlpha(endAlpha);
            } else {
                {
                    mShortGradationPaint.setAlpha(255);
                    mLongGradationPaint.setAlpha(255);
                    mShortGradationPaint.setColor(gradationColor);
                    mLongGradationPaint.setColor(Color.parseColor("#FF4AA5FD"));
                }
            }

這里還有一個(gè)難點(diǎn)就是結(jié)尾處的漸變值如何設(shè)置,因?yàn)榻Y(jié)尾處的距離超過了0~255范圍,而且這個(gè)漸變值需要和開始部分的透明值保持對(duì)應(yīng)并且是逐漸變小,開始處的透明值是逐漸增大的,比如:開始的透明值是1,2,3,4,那么結(jié)尾處的透明值就必須為4,3,2,1。處理的代碼為:

int endAlpha = Math.abs((int) ((mWidth + gradationGap) - distance));

這里我們可以舉個(gè)例子說明下,比如1,2,3,4,5,6,7,8,9,10 當(dāng)處于2的時(shí)候distance為2,7的時(shí)候distance為7,gradationGap為1,mWidth為10,我們想要把7,8,9,10映射成4,3,2,1,只需要使用:(10+1)-distance(7,8,9,10)就行了,讀者可以去計(jì)算試試。

事件的處理

我們滑動(dòng)屏幕時(shí)判斷如果是橫向滑動(dòng),則使用Scroll滾動(dòng)到我們想要滾動(dòng)的刻度。如果有慣性滾動(dòng),那么慣性滾動(dòng)后,再自動(dòng)吸附到最近的一個(gè)刻度上即可:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int action = event.getAction();
        final int x = (int) event.getX();
        final int y = (int) event.getY();

        Log.d(TAG, "onTouchEvent: " + action);
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mScroller.forceFinished(true);
                mDownX = x;
                isMoved = false;
                isPlayTipAnim = false;
                if (valueAnimator != null) {
                    valueAnimator.cancel();
                }
                break;

            case MotionEvent.ACTION_MOVE:
                final int dx = x - mLastX;
                //判斷是否已經(jīng)滑動(dòng)
                if (!isMoved) {
                    final int dy = y - mLastY;

                    // 滑動(dòng)的觸發(fā)條件,水平滑動(dòng)大于垂直滑動(dòng),滑動(dòng)距離大于閾值
                    if (Math.abs(dx) < Math.abs(dy) || Math.abs(x - mDownX) < TOUCH_SLOP) 
                    {
                        break;
                    }

                    isMoved = true;
                }

                mCurrentDistance -= dx;
                calculateValue();
                break;
            case MotionEvent.ACTION_UP:
                // 計(jì)算速度:使用1000ms 為單位
                mVelocityTracker.computeCurrentVelocity(1000, MAX_FLING_VELOCITY);
                // 獲取速度,速度有方向性,水平方向,左滑為負(fù),右滑為正
                int xVelocity = (int) mVelocityTracker.getXVelocity();
                // 達(dá)到速度則慣性滑動(dòng),否則緩慢滑動(dòng)到刻度
                if (Math.abs(xVelocity) >= MIN_FLING_VELOCITY) {
                    mScroller.fling((int) mCurrentDistance, 0, -xVelocity, 0,
                            0, (int) mNumberRangeDistance, 0, 0);
                    invalidate();
                } else {
                    scrollToGradation();
                }
                break;
        }

        mLastX = x;
        mLastY = y;
        return true;
    }

根據(jù)滑動(dòng)的距離計(jì)算處需要滾動(dòng)的刻度即可:

  private void scrollToGradation() {
        mCurrentNumber = mMinNumber + Math.round(mCurrentDistance / gradationGap) *
         mNumberUnit;
        // 算出的值邊界設(shè)置,如果當(dāng)前的值小于最小值,則選最小值,如果當(dāng)前的值大于最大值,則取最大值
       
        mCurrentNumber = Math.min(Math.max(mCurrentNumber, mMinNumber), mMaxNumber);
        mCurrentDistance = (float) (mCurrentNumber - mMinNumber) / mNumberUnit * 
        gradationGap;
        currentValue = mCurrentNumber / 10f; // 當(dāng)前的值是放大了10倍處理的,所以回調(diào)值的時(shí)候需要
        縮小10倍
        if (mValueChangedListener != null) {
            mValueChangedListener.onValueChanged(currentValue);
        }

        // 播放音效和震動(dòng)效果
        playSoundEffect();
        startVibration();
       // 觸發(fā)重繪
        invalidate();
    }

回調(diào)值給用戶

在滾動(dòng)的時(shí)候和計(jì)算值的時(shí)候?qū)⒅祷卣{(diào)給調(diào)用者

 /**
     * 當(dāng)前值變化監(jiān)聽器
     */
    public interface OnValueChangedListener {
        void onValueChanged(float value);
    }
  /**
     * 根據(jù)distance距離,計(jì)算數(shù)值
     */

    private void calculateValue() {
        // 限定范圍在最大值與最小值之間
        mCurrentDistance = Math.min(Math.max(mCurrentDistance, 0), mNumberRangeDistance);
        mCurrentNumber = mMinNumber + (int) (mCurrentDistance / gradationGap) * 
        mNumberUnit;
        // 因?yàn)橹捣糯罅?0倍處理,所以回調(diào)值的時(shí)候需要縮小10倍
        currentValue = mCurrentNumber / 10f;
        Log.d(TAG, "currentValue: " + currentValue + ",mCurrentDistance: "
                + mCurrentDistance + " ,mCurrentNumber: " + mCurrentNumber);
        if (mValueChangedListener != null) {
            mValueChangedListener.onValueChanged(currentValue);
        }

        invalidate();
    }
    private void scrollToGradation() {
        mCurrentNumber = mMinNumber + Math.round(mCurrentDistance / gradationGap) * 
        mNumberUnit;
        // 算出的值邊界設(shè)置,如果當(dāng)前的值小于最小值,則選最小值,如果當(dāng)前的值大于最大值,則取最大值
       
        mCurrentNumber = Math.min(Math.max(mCurrentNumber, mMinNumber), mMaxNumber);
        mCurrentDistance = (float) (mCurrentNumber - mMinNumber) / mNumberUnit * 
        gradationGap;
        currentValue = mCurrentNumber / 10f; // 當(dāng)前的值是放大了10倍處理的,所以回調(diào)值的時(shí)候需要
       // 縮小10倍
        if (mValueChangedListener != null) {
            mValueChangedListener.onValueChanged(currentValue);
        }

        // 播放音效和震動(dòng)效果
        playSoundEffect();
        startVibration();

        invalidate();
    }

總結(jié)

本文主要介紹了一個(gè)RulerSeekBar的自定義View,文中只介紹了關(guān)鍵的實(shí)現(xiàn)部分,其他細(xì)節(jié)部分讀者感興趣可以閱讀源碼,源碼的地址為:RulerSeekBar 自定義View的地址,控件使用的是Java語言編寫,雖然現(xiàn)在Android開發(fā)中Kotlin是扛把子,但是由于是給只會(huì)使用JAVA的用戶開發(fā)的控件,所以我使用了JAVA語言,但是Kotlin也能使用,并且如果讀者有時(shí)間可以使用kotlin將這個(gè)控件實(shí)現(xiàn)一下,原理基本一樣,就是使用的語法不同而已。

以上就是Android自定義View實(shí)現(xiàn)帶音效和震動(dòng)的SeekBar的詳細(xì)內(nèi)容,更多關(guān)于Android自定義View實(shí)現(xiàn)SeekBar的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論