1. 程式人生 > >HorizontalNumberView與seekbar共享滑動的自定義view

HorizontalNumberView與seekbar共享滑動的自定義view

這是一個簡單的,能與seekbar共享滑動的自定義view

我的部落格:詳解

簡單分析:

  1. 初始化一些引數,設定資料,準備在onDraw方法中連續繪製TextView
  2. 計算出TextView繪製的座標點,根據左滑還是右滑設定TextView之間間隙,大小,顏色
  3. 將seekbar注入進來,根據對調監聽設定當前位置,進行重繪,繪製最新的陣列列表

實現難點:

  1. 計算出繪製陣列寬度與高度
  2. TextView繪製座標與方式
  3. seekbar回撥,改變哪一些值可以達到預期的滑動效果
  4. 往左滑計算方式,往右滑計算方式
  5. 設定不同狀態下TextViewde引數

自定義View分析:

HorizontalNumberView

  1. 首先,初始化構造器與必要引數


        public class HorizontalNumberView extends View implements SeekBar.OnSeekBarChangeListener {

          private static final int SPACE_NUM = 2;

          private static String[] NUMBERS = new String[] {
              "1", "2", "3", "4", "5", "6", "7"
, "8", "9", "10" }; private static int mNormalTextColor; private static int mSelectTextColor; private int mNormalTextSize = 32; private int mSelectTextSize = 60; private int mNumberMax; private SeekBar mSeekBar; private
int mItemHeight; private int mItemWidth; private int mOffset; private int mCurrentProgress = 0; private int mBeforeProgress = 0; private int mCurrentIndex = 1; private double mCurrentValue; private Paint mPaint; private Paint mSelectPaint; private Rect mBounds; private OnSeekBarChangeListener mOnSeekBarChangeListener; public HorizontalNumberView(Context context) { this(context, null); } public HorizontalNumberView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public HorizontalNumberView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mNumberMax = NUMBERS.length + SPACE_NUM; mNormalTextColor = getResources().getColor(R.color.number_view_normal_text_color); mSelectTextColor = getResources().getColor(R.color.number_view_select_text_color); //init text paint mPaint = new Paint(); mPaint.setColor(mNormalTextColor); mPaint.setAntiAlias(true); mPaint.setTextSize(mNormalTextSize); //init color paint mSelectPaint = new Paint(); mSelectPaint.setColor(mSelectTextColor); mSelectPaint.setAntiAlias(true); mSelectPaint.setTextSize(mSelectTextSize); //init text bound rect mBounds = new Rect(); }
  1. 獲取PullRelativeLayoutState的狀態值,根據它來判斷是否處理事件:自己處理,子View處理,不處理等
  2. OnScrollStateChangeListener把ScrollView滾動高度狀態回調出去,給外面使用

HeaderFrameLayout



    public class HeaderFrameLayout extends FrameLayout {

        private Scroller mScroller;
        private int mHeight;
        private boolean isOpen;

        public HeaderFrameLayout(Context context) {
            super(context);
            init();
        }

        public HeaderFrameLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }

        public HeaderFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }

        private void init() {
            mScroller = new Scroller(getContext());
        }

        @Override
        public void computeScroll() {
            if (mScroller.computeScrollOffset()) {
                scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
                postInvalidate();
            }
        }

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            mHeight = getMeasuredHeight();
        }

        public void setOpen(boolean flag) {
            isOpen = flag;
        }

        public boolean isOpen() {
            return isOpen;
        }

        public void open() {
            if (!isOpen) {
                return;
            }
            smoothScrollTo(0, mHeight, 0, -mHeight, 800);
            isOpen = false;
        }

        private void smoothScrollTo(int startX, int startY,
                                    int dx, int dy, int duration) {
            mScroller.startScroll(startX, startY, dx, dy, duration);
            invalidate();
        }
    }



  1. 計算TextView寬高,注入seekbar設定監聽,設定必要引數

          @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            //init text width and height
            mItemHeight = getMeasuredHeight();
            mItemWidth = getMeasuredWidth() / mNumberMax;

            //check set seek bar
            if (mSeekBar == null) {
              throw new RuntimeException("you must call set seekBar method!");
            }
            mSeekBar.setOnSeekBarChangeListener(this);
          }


          public void setSeekBar(SeekBar seekBar) {
            mSeekBar = seekBar;
            mSeekBar.setMax((NUMBERS.length - 1) * 10);//set seekBar max value
          }


          @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            mCurrentProgress = progress;
            mCurrentIndex = (mCurrentProgress / NUMBERS.length) + 1;
            mCurrentValue = mCurrentIndex + ((mCurrentProgress % 10) / 10.0f);
            invalidate();

            if (mOnSeekBarChangeListener != null) {
              mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
              mOnSeekBarChangeListener.onStateChangeBack(mCurrentProgress, mCurrentIndex,
                  getCurrentValue());
            }
          }



  1. onDraw方法中根據左滑還是右滑設定TextView距離,即實際的位置


          @Override protected void onDraw(Canvas canvas) {
            //start draw text
            for (int i = 0; i < NUMBERS.length; i++) {
              String text = NUMBERS[i];
              int textIndex = i + 1;

              if (mCurrentProgress > 0 && mCurrentProgress > mBeforeProgress) {//left to right slip

                //item % value
                float value = (mCurrentProgress % 10) / 10.0f;

                if (textIndex - mCurrentIndex == 0) {//current text
                  mOffset = (int) (mOffset + mItemWidth - value * mItemWidth);
                }

                if (textIndex - mCurrentIndex == 1) {//current+1
                  mOffset += mItemWidth;
                }

                if (textIndex - mCurrentIndex == 2) {//current+2
                  mOffset = (int) (mOffset + value * mItemWidth);
                }
              } else if (mCurrentProgress > 0 && mCurrentProgress < mBeforeProgress) {//right to left slip

                //item % value
                float value = 1 - ((mCurrentProgress % 10) / 10.0f);

                if (textIndex - mCurrentIndex == 0 && mCurrentIndex != 1) {//current text
                  mOffset += mItemWidth;
                }

                if (textIndex - mCurrentIndex == -1) {//current - 1
                  mOffset = (int) (mOffset + value * mItemWidth);
                }

                if (textIndex - mCurrentIndex == 1 && mCurrentIndex != 1) {//current + 1
                  mOffset = (int) (mOffset + mItemWidth - value * mItemWidth);
                }
              }

              //text width
              int textX = (int) (mItemWidth * 0.5f + i * mItemWidth + mOffset);
              mPaint.getTextBounds(text, 0, text.length(), mBounds);

              //text height
              float textHeight = mBounds.height();
              int textY = (int) (mItemHeight * 0.5f + textHeight * 0.5f);
              float fontHeight = mPaint.getFontMetrics().bottom - mPaint.getFontMetrics().top;
              float posValue = fontHeight / 2 - mPaint.getFontMetrics().descent - 10;

              //text draw
              if (textIndex == mCurrentIndex) {
                canvas.drawText(text, textX, textY + posValue, mSelectPaint);
              } else {
                canvas.drawText(text, textX, textY + posValue, mPaint);
              }
            }

            //reset params
            mOffset = 0;
            mBeforeProgress = mCurrentProgress;
          }




onDraw方法為核心內容,必要的地方都有註釋,實現方式可能有很多種,分析就到這裡。

再來看一次效果圖:

10/31/2016 12:49:08 AM