編輯:關於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基礎——屬性動畫賞析
經常看一些大神的博客,大多數大神開篇都輕談一些國內比較專注的事和一些身邊瑣事,以表自己心情感悟。像我這種菜雞就直接步入正題吧。畢竟這東西就這麼簡單。Android動畫效果
Android開發之開發者頭條(一)啟動頁實現
廢話就不多說了,開始今天的正題,帶你實現開發者頭條APP的啟動頁。一.老規矩,先上效果圖從效果圖中我們可以看出,整個滑動的界面就是一個ViewPager實現,然後監聽Vi
AndroidStudio的快速入門
前言 心好疼:昨晚寫完了這篇博客一半,今天編輯的時候網絡突然斷了,我的文章就這樣沒了,但是為了Developer的使用AS這款IDE可以快速上手,我還是繼續進行詳解
Android編程實現的重力感應示例代碼
本文實例講述了Android編程實現的重力感應效果。分享給大家供大家參考,具體如下:android中的很多游戲的游戲都使用了重力感應的技術,就研究了一下重力感應以屏幕的左