編輯:關於Android編程
Android 自定義雙向滑動SeekBar ,一些需要價格區間選擇的App可能需要用到
1. 自定義MySeekBar 繼承 View,先給一張效果圖。
2.原理:自定義attrs屬性,從布局中獲取SeekBar最小值、坐標點個數、2點間代表的數值。
3.由SeekBar最小值、坐標點個數、2點間代表的數值確定 每個坐標點的所代表的數值。
4.onMeasure()方法中設置MySeekBar長寬比。
5.onSizeChanged()方法中計算滑動指示器半徑、設置每個坐標點的坐標。
6.onDraw()方法中依次畫背景線、2個指示器間的區間線、2個滑動指示器。
7.onTouchEvent()方法中,Down判斷是否命中滑動指示器,Move時在命中的條件下進行有限滑動,Up時根據是否有滑動啟動屬性動畫。
8.MySeekBar 內部類 CircleIndicator代表滑動指示器,Point代表坐標類以及OnSeekFinishListener回調接口。
9.首先給出自定義屬性,後面會使用到。values文件夾建立attrs xml。
10.再看下布局。
public class MySeekBar extends View implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener { /** * SeekBar最小值 */ int minValue; /** * SeekBar共包含多少個坐標點 */ int pointCount; /** * 每個分段代表的數值 */ private int perValue; /** * 分段的端點坐標記錄數組,長度等於pointCount */ Point[] mPoints; /** * SeekBar長寬比 */ float mLWRatio = 1f / 10f; CircleIndicator mLeftCI;//左側滑動指示器 CircleIndicator mRightCI;//右側滑動指示器 int mR;//滑動指示器半徑 int mPadding = 5;//指定的Padding Paint linePaint = new Paint(Paint.ANTI_ALIAS_FLAG); Paint indicatorPaint = new Paint(Paint.ANTI_ALIAS_FLAG); float LINE_WIDTH = 8F;//線寬 /** * 滑動指示器顏色 */ private int indicatorColor; /** * 2個滑動器之間的線顏色 */ private int indicatorLineColor; /** * 背景線顏色 */ private int backLineColor; /** * 圓形區域 */ private RectF mRectF; /** * 當前SeekBar是否有滑動指示器處於被滑動狀態 */ boolean isSelected = false; /** * 屬性動畫是否正在執行 */ boolean isPlaying; /** * 與滑動指示器最近的mPoints index值 */ private int mCloseIndex; /** * 動畫時長 */ private final long ANIM_DURATION = 200; public MySeekBar(Context context, AttributeSet attrs) { super(context, attrs); //無視padding屬性 使用內部定義的mPadding setPadding(0, 0, 0, 0); init(context, attrs); } private void init(Context context, AttributeSet attrs) { //獲取自定義屬性 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MySeekBar); perValue = a.getInt(R.styleable.MySeekBar_per_value, 0); minValue = a.getInt(R.styleable.MySeekBar_min, 0); pointCount = a.getInt(R.styleable.MySeekBar_point_count, 0); backLineColor = a.getColor(R.styleable.MySeekBar_back_line_color, Color.LTGRAY); indicatorLineColor = a.getInt(R.styleable.MySeekBar_indicator_line_color, Color.GREEN); indicatorColor = a.getInt(R.styleable.MySeekBar_indicator_color, Color.GRAY); a.recycle(); //初始化SeekBar內部坐標對象 mPoints = new Point[pointCount]; for (int i = 0; i < mPoints.length; i++) { mPoints[i] = new Point(minValue + i * perValue); } //初始化2個滑動指示器 默認左邊的位於mPoints數組第一個,右邊位於mPoints數組最後一個 mLeftCI = new CircleIndicator(); mLeftCI.setPoint(mPoints[0]); mRightCI = new CircleIndicator(); mRightCI.setPoint(mPoints[mPoints.length - 1]); //初始化Paint linePaint.setStyle(Paint.Style.STROKE); linePaint.setStrokeCap(Paint.Cap.ROUND); linePaint.setStrokeWidth(LINE_WIDTH); //初始化滑動指示器Paint indicatorPaint.setStyle(Paint.Style.FILL); indicatorPaint.setColor(indicatorColor); //設置陰影 注意:當前view需要添加 android:layerType="software" indicatorPaint.setShadowLayer(5, 2, 2, Color.LTGRAY); //初始化圓形區域 mRectF = new RectF(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int size = MeasureSpec.getSize(widthMeasureSpec); int heightSize = (int) (size * mLWRatio); int spec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY); //設置View的長寬比 setMeasuredDimension(widthMeasureSpec, spec); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); //CircleIndicator半徑 mR = h / 2 - mPadding; //指定滑動指示器半徑 mLeftCI.setR(mR); mRightCI.setR(mR); //分段點坐標 mPoints數組 均分SeekBar寬度 int y = h / 2; int perWidth = (w - 2 * mPadding - 2 * mR) / (mPoints.length - 1); for (int i = 0; i < mPoints.length; i++) { mPoints[i].setX(mPadding + mR + i * perWidth); mPoints[i].setY(y); } //更新一下 滑動指示器當前的坐標 mLeftCI.setPoint(mLeftCI.getPoint()); mRightCI.setPoint(mRightCI.getPoint()); //回調當前Activity 告知2個滑動指示器的屬性 callBack(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(Color.WHITE); if (mPoints.length >= 2) { //畫背景線 linePaint.setColor(backLineColor); canvas.drawLine(mPoints[0].getX(), mPoints[0].getY() , mPoints[mPoints.length - 1].getX(), mPoints[mPoints.length - 1].getY(), linePaint); //畫區間線 linePaint.setColor(indicatorLineColor); canvas.drawLine(mLeftCI.getCurX(), mLeftCI.getCurY(), mRightCI.getCurX(), mRightCI.getCurY(), linePaint); //畫左邊的Indicator mRectF.set(mLeftCI.getCurX() - mR, mLeftCI.getCurY() - mR, mLeftCI.getCurX() + mR, mLeftCI.getCurY() + mR); canvas.drawOval(mRectF, indicatorPaint); //畫右邊的Indicator mRectF.set(mRightCI.getCurX() - mR, mRightCI.getCurY() - mR, mRightCI.getCurX() + mR, mRightCI.getCurY() + mR); canvas.drawOval(mRectF, indicatorPaint); } } @Override public boolean onTouchEvent(MotionEvent event) { //如果當前正在執行動畫 則忽略用戶點擊 if (isPlaying) { return true; } float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //檢查當前按下的坐標是否命中滑動指示器 isSelected = checkPoint(x, y); break; case MotionEvent.ACTION_MOVE: //在命中的情況下,滑動指示器會在有限范圍內滑動 move(x); break; case MotionEvent.ACTION_UP: //當Up時,檢查是否需要開啟屬性動畫 reset(); break; } //如果已經有滑動指示器呗滑動了,就需要刷新當前View了 if (isSelected) { invalidate(); } return true; } private void reset() { //重置滑動狀態 isSelected = false; //執行動畫 if (mLeftCI.isTouch()) { mCloseIndex = getCloseIndex(mLeftCI); statAnim(mLeftCI, mCloseIndex); } if (mRightCI.isTouch()) { mCloseIndex = getCloseIndex(mRightCI); statAnim(mRightCI, mCloseIndex); } mLeftCI.setIsTouch(false); mRightCI.setIsTouch(false); } /** * 加載動畫 */ private void statAnim(CircleIndicator rightCI, int closeIndex) { ObjectAnimator animator = ObjectAnimator.ofInt(rightCI, "curX", rightCI.getCurX(), mPoints[closeIndex].getX()); animator.addUpdateListener(this); animator.addListener(this); animator.setDuration(ANIM_DURATION); animator.start(); } /** * 獲取距離 該Indicator最近的 坐標點 * * @param indicator * @return */ private int getCloseIndex(CircleIndicator indicator) { int curX = indicator.getCurX(); int distance = Integer.MAX_VALUE; int index = 0; //循環找出距離當前indicator 最近的坐標對象 for (int i = 0; i < mPoints.length; i++) { int abs = Math.abs(curX - mPoints[i].getX()); if (abs <= distance) { distance = abs; index = i; } } if (indicator.equals(mLeftCI)) { //如果是左邊的Indicator,那麼最大的index不能超過 右邊的Indicator所屬的坐標index if (mPoints[index].getX() >= mRightCI.getCurX()) { index--; } return index; } if (indicator.equals(mRightCI)) { //同理 if (mPoints[index].getX() <= mLeftCI.getCurX()) { index++; } return index; } return index; } private void move(float x) { if (mLeftCI.isTouch()) { //如果左邊的Indicator呗拖拽,其x坐標應該在 第一個坐標 和右邊的Indicator 之間 //即限定 indicator可移動的范圍 if (x >= mPoints[0].getX() && x < mRightCI.getCurX()) { mLeftCI.setCurX((int) x); } return; } //同理 if (mRightCI.isTouch()) { if (x <= mPoints[mPoints.length - 1].getX() && x > mLeftCI.getCurX()) { mRightCI.setCurX((int) x); } } } /** * 檢查 Down的x y是否命中 CircleIndicator,如果命中更新屬性 * * @param x * @param y * @return true 命中, false 為命中 */ private boolean checkPoint(float x, float y) { boolean containsL = mLeftCI.getRect().contains((int) x, (int) y); if (containsL) { mLeftCI.setIsTouch(true); return true; } boolean containsR = mRightCI.getRect().contains((int) x, (int) y); if (containsR) { mRightCI.setIsTouch(true); return true; } return false; } /** * 設置2個Indicator的位置 * * @param left * @param right */ public void setPos(int left, int right) { if (right > left && right >= 0 && left >= 0 && right <= pointCount) { mLeftCI.setPoint(mPoints[left]); mRightCI.setPoint(mPoints[right]); callBack(); invalidate(); } } //addUpdateListener動畫回調部分 @Override public void onAnimationUpdate(ValueAnimator animation) { //每次修改完成屬性 就應該刷新當前View invalidate(); } //addListener動畫回調部分 @Override public void onAnimationStart(Animator animation) { //動畫執行開始 isPlaying = true; } @Override public void onAnimationEnd(Animator animation) { CircleIndicator indicator = (CircleIndicator) ((ObjectAnimator) animation).getTarget(); //更新被移動Indicator 坐標屬性 indicator.setPoint(mPoints[mCloseIndex]); //動畫執行結束 isPlaying = false; //回調Activity告知 滑動指示器信息 callBack(); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } /** * SeekBar被拖拽的滑動指示器 */ public class CircleIndicator { int curX;//indicator x坐標 int curY;//indicator y坐標 int mR; //indicator 半徑 /** * 當前滑動指示器附著的坐標點 */ Point mPoint; /** * 是否被觸摸 */ boolean isTouch; /** * Indicator 所包含的矩形區域 */ Rect mRect = new Rect(); /** * 獲取當前Indicator所在的矩形區域 * * @return Rect */ public Rect getRect() { mRect.set(curX - mR, curY - mR, curX + mR, curY + mR); return mRect; } public boolean isTouch() { return isTouch; } public void setIsTouch(boolean isTouch) { this.isTouch = isTouch; } public Point getPoint() { return mPoint; } public void setPoint(Point point) { mPoint = point; curX = point.getX(); curY = point.getY(); invalidate(); } public void setPosition(Point point) { curX = point.getX(); curY = point.getY(); } public int getR() { return mR; } public void setR(int r) { mR = r; } public int getCurX() { return curX; } public void setCurX(int curX) { this.curX = curX; } public int getCurY() { return curY; } public void setCurY(int curY) { this.curY = curY; } } /** * SeekBar內部的 坐標類 */ public class Point { /** * 當前坐標點所代表的數值 */ int mark; int x;//x坐標 int y;//y坐標 public Point(int mark) { this.mark = mark; } public int getMark() { return mark; } public void setMark(int mark) { this.mark = mark; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } } public interface OnSeekFinishListener { void seekPos(CircleIndicator left, CircleIndicator right); } OnSeekFinishListener mListener; public void setListener(OnSeekFinishListener listener) { mListener = listener; } private void callBack() { if (mListener != null) mListener.seekPos(mLeftCI, mRightCI); } }
12.通過setPos()方法來控制MySeekBar 滑動指示器所處的位置。
13.代碼裡有很多注釋,應該還算清楚,給自己留個底,供參考。完~~
經常看一些大神的博客,大多數大神開篇都輕談一些國內比較專注的事和一些身邊瑣事,以表自己心情感悟。像我這種菜雞就直接步入正題吧。畢竟這東西就這麼簡單。Android動畫效果
廢話就不多說了,開始今天的正題,帶你實現開發者頭條APP的啟動頁。一.老規矩,先上效果圖從效果圖中我們可以看出,整個滑動的界面就是一個ViewPager實現,然後監聽Vi
前言 心好疼:昨晚寫完了這篇博客一半,今天編輯的時候網絡突然斷了,我的文章就這樣沒了,但是為了Developer的使用AS這款IDE可以快速上手,我還是繼續進行詳解
本文實例講述了Android編程實現的重力感應效果。分享給大家供大家參考,具體如下:android中的很多游戲的游戲都使用了重力感應的技術,就研究了一下重力感應以屏幕的左