失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > Android左右滑动控件实现开关的切换效果

Android左右滑动控件实现开关的切换效果

时间:2019-02-16 16:20:45

相关推荐

Android左右滑动控件实现开关的切换效果

/**

* 开关控件,通过左右滑动控件实现开关的切换效果 <br>

* 使用时需要设置开关状态监听{@link OnSwitchStateChangeListener} <br>

* 切换开关状态有2种方式: <br>

* <p>

* 1.{@link #on()}, {@link #off()}, 该方式分开操作, 并且带头动作动画 <br>

* <p>

* 2.{@link #turn(boolean, boolean), 该方式通过传入布尔值控制开关以及是否显示切换动画 <br>

*

*/

public class UISwitch extends View {

/**

* 用来监听开关状态变化的接口

*

*/

public interface OnSwitchStateChangeListener {

public void onChange(boolean state);

}

/** 最大滑动时间 **/

private static final int MAX_SETTLE_DURATION = 5000;

private static final int MESSAGE_SCROLL = 1;

private static final int DEFAULE_TRACKER_NEXT = 600;

/** 开, 关, 按钮 **/

private Bitmap on, off, handle;

private Paint mPaint;

private Paint handlePaint;

private Scroller mScroll;

private VelocityTracker mVelocityTracker;

private OnSwitchStateChangeListener l;

private int width, height;

private int mLastX;

/** 滑动偏移量 **/

private int mOffset;

/** 最大最小偏移 **/

private int mMaxOffset, mMinOffset;

/** 开关状态 **/

private boolean mState;

private boolean mOnEnable;

/** 是否进行了拖拽 **/

private boolean mIsBeingDragged;

/** 触摸优化 **/

private int mTouchSlop;

private int drawableIds[] = new int[] { R.drawable.switch_btn_on,

R.drawable.switch_btn_off, R.drawable.switch_btn_handle };

/** 滑动事件 **/

private Handler scrollHandler = new Handler() {

@Override

public void handleMessage(Message msg) {

if (puteScrollOffset()) {

final int currX = mScroll.getCurrX();

final int finalX = mScroll.getFinalX();

if (finalX >= 0) {

mOffset -= currX;

} else {

mOffset += -currX;

}

// 校验偏移量

mOffset = Math.min(Math.max(mMinOffset, mOffset), mMaxOffset);

sendEmptyMessage(msg.what);

invalidate();

}

}

};

public UISwitch(Context context) {

this(context, null);

}

public UISwitch(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public UISwitch(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

TypedArray a = context.obtainStyledAttributes(attrs,

R.styleable.UISwitch, defStyle, 0);

if (a != null) {

drawableIds[0] = a.getResourceId(

R.styleable.UISwitch_ui_switch_on_src,

R.drawable.switch_btn_on);

drawableIds[1] = a.getResourceId(

R.styleable.UISwitch_ui_switch_off_src,

R.drawable.switch_btn_off);

drawableIds[2] = a.getResourceId(

R.styleable.UISwitch_ui_switch_handle_src,

R.drawable.switch_btn_handle);

mState = a.getBoolean(R.styleable.UISwitch_ui_switch_state, false);

a.recycle();

}

init(context);

}

private void init(Context context) {

on = getBitmapFor(drawableIds[0]);

off = getBitmapFor(drawableIds[1]);

handle = getBitmapFor(drawableIds[2]);

mScroll = new Scroller(context, new AccelerateInterpolator());

final ViewConfiguration configuration = ViewConfiguration.get(context);

mTouchSlop = ViewConfigurationCompat

.getScaledPagingTouchSlop(configuration);

mPaint = new Paint();

mPaint.setAntiAlias(true);

mPaint.setDither(true);

mPaint.setFilterBitmap(true);

handlePaint = new Paint();

handlePaint.setAntiAlias(true);

handlePaint.setDither(true);

handlePaint.setFilterBitmap(true);

setClickable(true);

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

if (on == null || off == null || handle == null) {

throw new RuntimeException("invalid image resources");

}

width = Math.max(Math.max(on.getWidth(), off.getWidth()),

handle.getWidth());

height = Math.max(Math.max(on.getHeight(), off.getHeight()),

handle.getHeight());

setMeasuredDimension(width, height);

mMinOffset = 0;

mMaxOffset = width - handle.getWidth();

setState(mState);

// final int newW = MeasureSpec.makeMeasureSpec(width,

// MeasureSpec.EXACTLY);

// final int newH = MeasureSpec.makeMeasureSpec(height,

// MeasureSpec.EXACTLY);

//

// super.onMeasure(newW, newH);

}

@Override

protected void onDraw(Canvas canvas) {

final Bitmap bmp = Bitmap.createBitmap(width, height, Config.ARGB_8888);

final Canvas c = new Canvas(bmp);

// 画开图像

c.drawBitmap(on, getSwitchLeft() - on.getWidth() + handle.getWidth()

+ mOffset, getSwitchTop(), mPaint);

// 画关图像

c.drawBitmap(off, getSwitchLeft() + mOffset, getSwitchTop(), mPaint);

// 画把手

c.drawBitmap(handle, getSwitchLeft() + mOffset, getSwitchTop(),

handlePaint);

// 圆角处理

// canvas.drawBitmap(bmp, m, mPaint);

canvas.drawBitmap(toRoundCorner(bmp, bmp.getHeight() / 2),

getSwitchLeft(), getSwitchTop(), mPaint);

}

/**

* 设置开关资源图片

* <br>(the bitmap array sequence must be 'on', 'off', 'handle')

*

* @param bmp

*/

public void setResourceBitmaps(Bitmap bmp[]) {

if (bmp == null) {

throw new NullPointerException("the resource bitmap do not null");

}

if (bmp.length != 3) {

throw new ArrayIndexOutOfBoundsException("the resource bitmap array length must be 3");

}

on = bmp[0];

off = bmp[1];

handle = bmp[2];

width = Math.max(Math.max(on.getWidth(), off.getWidth()),

handle.getWidth());

height = Math.max(Math.max(on.getHeight(), off.getHeight()),

handle.getHeight());

onMeasure(0, 0);

invalidate();

}

@Override

public boolean onTouchEvent(MotionEvent event) {

// 如果速度监听对象为空

if (mVelocityTracker == null) {

// 获得该对象的实例

mVelocityTracker = VelocityTracker.obtain();

}

mVelocityTracker.addMovement(event);

final int action = event.getAction();

final int currX = (int) MotionEventCompat.getX(event, 0);

switch (action & MotionEventCompat.ACTION_MASK) {

case MotionEvent.ACTION_DOWN:

//不让父控件获取手势

if (getParent() != null) {

getParent().requestDisallowInterceptTouchEvent(true);

}

lightness(handlePaint, 0.9f);

if (puteScrollOffset()) {

mScroll.abortAnimation();

}

mLastX = currX;

break;

case MotionEvent.ACTION_MOVE:

final int dx = currX - mLastX;

if (!mIsBeingDragged) {

if (Math.abs(dx) > mTouchSlop / 4) {

mIsBeingDragged = true;

}

} else {

mOffset += currX - mLastX;

// 校验偏移量

mOffset = Math.min(Math.max(mMinOffset, mOffset), mMaxOffset);

mLastX = currX;

}

break;

case MotionEvent.ACTION_UP :

//不让父控件获取手势

if (getParent() != null) {

getParent().requestDisallowInterceptTouchEvent(false);

}

final VelocityTracker velocityTracker = mVelocityTracker;

puteCurrentVelocity(1000,

ViewConfiguration.get(getContext())

.getScaledMaximumFlingVelocity());

int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(

velocityTracker, 0);

if (Math.abs(initialVelocity) > DEFAULE_TRACKER_NEXT

|| !mIsBeingDragged) {

if (!mIsBeingDragged) {

initialVelocity = 0;

}

turn(!mState, initialVelocity);

} else {

final int halfOffset = mMaxOffset / 2;

turn(mOffset > halfOffset, 0);

}

mIsBeingDragged = false;

if (mVelocityTracker != null) {

// 回收资源

mVelocityTracker.recycle();

mVelocityTracker = null;

}

lightness(handlePaint, 1f);

break;

}

invalidate();

return super.onTouchEvent(event);

}

/**

* 通过提供一个起点和直线距离开始滑动

*

* @param startx

* 起点x坐标

* @param starty

* 起点y坐标

* @param dx

* x距离

* @param dy

* y距离

* @param duration

* 持续滑动时间

*/

private void startScroll(int startx, int starty, int dx, int dy,

int duration) {

mScroll.startScroll(startx, starty, dx, dy, duration);

scrollHandler.removeMessages(MESSAGE_SCROLL);

scrollHandler.sendEmptyMessage(MESSAGE_SCROLL);

}

/**

* 获取滑动持续时间

*

* @param dx

* 滑动距离

* @param velocity

* 滑动速度

* @return

*/

private int getScrollDuration(int dx, int velocity) {

final int width = getWidth();

final int halfWidth = width / 2;

final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);

final float distance = halfWidth + halfWidth

* distanceInfluenceForSnapDuration(distanceRatio);

int duration = 0;

velocity = Math.abs(velocity);

if (velocity > 0) {

duration = 4 * Math.round(1000 * Math.abs(distance / velocity));

} else {

duration = Math.abs(dx) * 4;

}

duration = Math.min(duration, MAX_SETTLE_DURATION);

return duration;

}

// We want the duration of the page snap animation to be influenced by the

// distance that

// the screen has to travel, however, we don't want this duration to be

// effected in a

// purely linear fashion. Instead, we use this method to moderate the effect

// that the distance

// of travel has on the overall snap duration.

private float distanceInfluenceForSnapDuration(float f) {

f -= 0.5f; // center the values about 0.

f *= 0.3f * Math.PI / 2.0f;

return (float) Math.sin(f);

}

/**

* 将图片设置为圆角

*/

private Bitmap toRoundCorner(Bitmap bitmap, int pixels) {

final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),

bitmap.getHeight(), Config.ARGB_8888);

final Canvas canvas = new Canvas(output);

final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());

final RectF rectF = new RectF(rect);

final Paint paint = new Paint();

paint.setAntiAlias(true);

paint.setDither(true);

canvas.drawARGB(0, 0, 0, 0);

canvas.drawRoundRect(rectF, pixels, pixels, paint);

paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));

canvas.drawBitmap(bitmap, rect, rect, paint);

paint.setStyle(Paint.Style.STROKE);

paint.setStrokeWidth(0);

paint.setStrokeCap(Cap.ROUND);

paint.setStrokeJoin(Join.ROUND);

canvas.drawRoundRect(rectF, pixels, pixels, paint);

return output;

}

/**

* 获取资源位图

*

* @param resId

* @return

*/

private Bitmap getBitmapFor(int resId) {

Options op = new Options();

op.inScaled = true;

Bitmap bmp = BitmapFactory.decodeResource(getResources(), resId, op);

return bmp;

}

@Override

public void setEnabled(boolean enabled) {

setClickable(false);

setFocusable(false);

super.setEnabled(enabled);

}

/**

* 开关开启,含有切换效果

*/

public void on() {

turn(true, 0);

}

/**

* 开关关闭,含有切换效果

*/

public void off() {

turn(false, 0);

}

/**

* 开关操作

*

* @param on

* @param smoothScroll

* True to smoothly scroll to the new item, false to transition

* immediately

*/

public void turn(boolean on, boolean smoothScroll) {

if (smoothScroll) {

turn(on, 0);

} else {

setState(on);

}

}

/**

* 开关操作,含有切换效果

*

* @param on

* true/false, 开/关, on/off

* @param vx

* 切换速度, 0为系统默认

*/

private void turn(boolean on, int vx) {

int distance;

if (on) {

distance = mOffset - mMaxOffset;

} else {

distance = mOffset;

}

if (on != mState) {

if (l != null) {

l.onChange(on);

}

}

mState = on;

if (puteScrollOffset()) {

mScroll.abortAnimation();

}

startScroll(0, 0, distance, 0, getScrollDuration(distance, vx));

}

/**

* 设置开关状态,无状态切换动画

*/

private void setState(boolean state) {

if (state != mState) {

if (l != null) {

l.onChange(state);

}

}

mState = state;

if (state) {

mOffset = mMaxOffset;

} else {

mOffset = 0;

}

invalidate();

}

/***

* 设置开关状态变化事件

*

* @param l

*/

public void setOnStateChangeListener(OnSwitchStateChangeListener l) {

this.l = l;

}

private int getSwitchLeft() {

return 0;

}

private int getSwitchRight() {

return width;

}

private int getSwitchTop() {

return 0;

}

private int getSwitchBottom() {

return height;

}

/**

* 调整画笔亮度

*

* @param paint

* 画笔

* @param light

* 亮度单位 , 正常为1f, 范围0f - 2f

*/

private void lightness(Paint paint, float light) {

final ColorMatrix cm = new ColorMatrix();

cm.setScale(light, light, light, 1);

paint.setColorFilter(new ColorMatrixColorFilter(cm));

}

}

如果觉得《Android左右滑动控件实现开关的切换效果》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。