()
Android仿ios吸边弹簧阻尼效果的移动组件SpringMovingView
更多其他页面-自定义View-实用功能合集:点击查看
Github项目地址: 点击跳转,欢迎fork收藏
功能简介
仿ios带有阻尼效果的可自由移动组件。靠近四边有吸附效果,超出四边具有弹簧阻尼效果,超出越多阻力越多,且释放时具有回弹效果。
相关文章讲解:
简单的自由移动组件SimpleMovingView实现 点击浏览
View.layout如何刷新控件位置?点击跳转
用layout方法刷新控件新位置后,为什么页面有新的组件添加后,会刷新页面,控件回到原来的位置 点击跳转
Android技术生活交流
----- qq群
Gif演示
实现步骤
1.创建SpringMovingView.java并继承一个view
2.对于实现可自由移动组件请看之前的文章如何写的. 点击浏览
3.实现吸边效果. 首先我们先设一个想要的吸附距离mAttach_Distance=20
,当组件移动与到屏幕四边的距离<=吸附距离时,视为吸附到四边。立即调用layout(left,top,right,bottom)
来刷新组件新位置的UI即可。具体看do_attach_boundary_effect()
4.例如:朝左移动组件,直到当与距离屏幕左边距离<=吸附距离。那么组件left=0,top=不变,right= 左边0+组件宽度,bottom=不变。
5.实现弹簧压缩阻尼,与释放回弹的效果.
6.弹簧压缩阻尼效果:当view随着手指移动超过屏幕四边后,开始减少手指移动赋予view新位置的值,从而达到用手指移动view时感觉像受到了阻碍,具体看do_spring_press_effect()
,
7.弹簧释放回弹效果:MotionEvent.ACTION_UP
手势抬起时,判断下当前组件所处的位置,是否超出了屏幕边界。如果超出了边界,则开始计算超出的部分(长,宽),和将要回弹的高度。具体看do_spring_release_effect()
。
8.这里主要是调用了位移动画达到实现回弹的效果TranslateAnimation
,
java代码
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
/**
* 作者:游丰泽
* 功能介绍:仿ios具有弹簧效果的自由移动组件,靠近四边带有吸附效果。
* 移动超出四边具有弹簧阻尼按压效果,且释放后会有弹簧回弹效果,最终落到边界
**/
public class SpringMovingView extends LinearLayout {
private OnClickListener mOnClickListener;
//手指按下时相对于屏幕的X,Y位置
private float mDownInScreenX, mDownInScreenY;
//手指按下时相对于组件的X,Y位置
private double mDownInViewX, mDownInViewY;
//手指移动产生X,Y的距离
private double mMoveDistanceX, mMoveDistanceY;
//新位置left top right bottom
private int mNewPosition_left, mNewPosition_top, mNewPosition_right, mNewPosition_bottom;
//屏幕长宽
private int mScreenHeight, mScreenWidth;
//弹簧释放时,弹起的X,Y
private double mSpringReleaseX, mSpringReleaseY;
//弹簧释放时,动画的起X,终X,起Y,终Y
private int mAnimStartX=0, mAnimEndX, mAnimStartY=0, mAnimEndY;
/**
* inner 吸附距离四边的最小距离
*/
private double mAttach_Distance =20;
/**
* 弹簧释放时,弹起的高度比例
*/
private double mSpringReleaseHeightRate =0.5;
/**
* 动画播放时间
*/
private int mAnimationDuringTime=300;
/**
* 动画结束后,组件的位置 left top right bottom
*/
private int mAnim_left =0, mAnim_top =0, mAnim_right =0, mAnim_bottom =0;
public SpringMovingView(Context mContext) {
super(mContext);
initView(mContext);
}
public SpringMovingView(Context mContext, @Nullable AttributeSet attrs) {
super(mContext,attrs);
initView(mContext);
}
public SpringMovingView(Context mContext, AttributeSet attrs, int defStyle)
{
super(mContext,attrs,defStyle);
initView(mContext);
}
/**
* 初始化View
* @param context
*/
private void initView(final Context context) {
DisplayMetrics dm= new DisplayMetrics();
WindowManager wm=(WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics( dm );
mScreenHeight =dm.heightPixels;
mScreenWidth =dm.widthPixels;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mDownInViewX =event.getX();
mDownInViewY =event.getY();
mDownInScreenX = event.getRawX();
mDownInScreenY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
//记录移动距离
mMoveDistanceX = event.getX() - mDownInViewX; //计算手指相对于View移动距离X
mMoveDistanceY = event.getY() - mDownInViewY; //计算手指相对于View移动距离Y
//组件新的位置left top right bottom
mNewPosition_left = (int) (getLeft() + mMoveDistanceX);
mNewPosition_right = (int) (getRight() + mMoveDistanceX);
mNewPosition_top = (int) (getTop() + mMoveDistanceY);
mNewPosition_bottom = (int) (getBottom() + mMoveDistanceY);
do_attach_boundary_effect(); //吸边效果 当组件靠近屏幕四边时会有吸附上去的效果
do_spring_press_effect(); //弹簧压缩效果 当组件超过屏幕四边后,继续移动会有阻尼效果
//防止超出容器边界,而无法进行动画操作
if(mNewPosition_left >(-1*getWidth()+1)&& mNewPosition_top >(-1*getHeight()+1)&& mNewPosition_bottom <(mScreenHeight +getHeight()-1)&& mNewPosition_right <(mScreenWidth +getWidth()-1)){
refreshNewPosition(mNewPosition_left, mNewPosition_top, mNewPosition_right, mNewPosition_bottom);
}
break;
case MotionEvent.ACTION_UP:
if(getLeft()<0||getRight()> mScreenWidth){ //当左 或 右超出边界时,视为触发了弹簧压缩,则计算弹簧释放后弹出的 X 距离
mSpringReleaseX = calculateSpringReleaseX(getLeft(),getRight())* mSpringReleaseHeightRate;
}else {
mSpringReleaseX=0;
}
if(getTop()<0||getBottom()> mScreenHeight){ //当上 或 下超出边界时,视为触发了弹簧压缩,则计算弹簧释放后弹出的 Y 距离
mSpringReleaseY = calculateSpringReleaseY(getTop(),getBottom())* mSpringReleaseHeightRate;
}else {
mSpringReleaseY=0;
}
do_spring_release_effect(); //弹簧回弹效果,当触发了弹簧压缩后,释放手势组件回弹,压缩越多,回弹越多
if(null != mOnClickListener) { //当实现了监听click接口
if (mDownInScreenX == event.getRawX() && mDownInScreenY == event.getRawY()) { //且view没有移动时,视为点击
mOnClickListener.isClick(true);
}
}
break;
}
return true; //返回true,为了截取手势事件,当前view进行处理
}
/**
* ios弹簧方法-压缩-通过放慢移动速度的方法(超出越多,放慢越多),从而达到压缩的效果
**/
private void do_spring_press_effect(){ //ios弹簧方法-压缩
if (getLeft() < 0) { //左放慢
mNewPosition_left = (int) (getLeft() + calculateSpringPressDistance(mMoveDistanceX, getLeft()-0));
mNewPosition_right = mNewPosition_left + getWidth();
} else if (getRight() > mScreenWidth) { //右放慢
mNewPosition_right = (int) (getRight() + calculateSpringPressDistance(mMoveDistanceX, getRight()-mScreenWidth));
mNewPosition_left = mNewPosition_right - getWidth();
}
if (getTop() < 0) { //上放慢
mNewPosition_top = (int) (getTop() + calculateSpringPressDistance(mMoveDistanceY, getTop()-0));
mNewPosition_bottom = mNewPosition_top + getHeight();
} else if (getBottom() > mScreenHeight) { //下放慢
mNewPosition_bottom = (int) (getBottom() + calculateSpringPressDistance(mMoveDistanceY, getBottom()-mScreenHeight));
mNewPosition_top = mNewPosition_bottom - getHeight();
}
}
//移动速度随着组件view超出屏幕边界越多,变的越慢
private double calculateSpringPressDistance(double distance, double overBoundaryDistance){
return 0.01*distance * Math.sqrt(Math.abs(overBoundaryDistance));
}
//记录一下组件view超出边界的W
private double calculateSpringReleaseX(double Left, double Right){
double W=0.0;
if(Left<0){ //左超边界
W= Left;
}else if( Left>0 && Right> mScreenWidth) { //右超边界
W= Right- mScreenWidth;
}
return W ;
}
//记录一下组件view超出边界的H
private double calculateSpringReleaseY(double Top, double Bottom){
double H=0.0;
if(Top<0){ //上超边界
H= Top;
}else if( Top>0 && Bottom> mScreenHeight) { //下超边界
H= Bottom- mScreenHeight;
}
return H ;
}
/**
* ios弹簧方法-释放
**/
private void do_spring_release_effect() { //ios弹簧方法-释放
//当开启弹簧效果,且任意一边超出屏幕边界
if(getLeft()<mSpringReleaseX||getRight()> mScreenWidth ||getTop()<mSpringReleaseY||getBottom()> mScreenHeight) {
mAnim_left =getLeft(); mAnim_top =getTop();
mAnim_right =(int)(mAnim_left +getWidth()); mAnim_bottom = (int) (mAnim_top + getHeight());
mAnimEndX =0;
mAnimEndY =0;
if (getLeft() < mSpringReleaseX) {
mAnimEndX = (int) (-1 * mSpringReleaseX * 3);
mAnim_left = 0;
mAnim_right =(int)(mAnim_left +getWidth());
} else if (getRight() > mScreenWidth) {
mAnimEndX = (int) ((-1 * mSpringReleaseX * 3));
mAnim_left = (int) (mScreenWidth - getWidth());
mAnim_right = (int) (mScreenWidth);
}
if (getTop() < mSpringReleaseY) {
mAnimEndY = (int) (-1 * mSpringReleaseY * 3);
mAnim_top = 0;
mAnim_bottom = (int) (mAnim_top + getHeight());
} else if (getBottom() > mScreenHeight) {
mAnimEndY = (int) (-1 * mSpringReleaseY * 3);
mAnim_bottom = (int) mScreenHeight;
mAnim_top = (int) (mAnim_bottom - getHeight());
}
animation(); //开始进行位移动画
}
}
/**
* 回弹位移动画
*/
private void animation(){
TranslateAnimation transAnim = new TranslateAnimation(mAnimStartX, mAnimEndX, mAnimStartY, mAnimEndY);
transAnim.setDuration(mAnimationDuringTime);
transAnim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
clearAnimation();
refreshNewPosition(mAnim_left, mAnim_top, mAnim_right, mAnim_bottom);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
startAnimation(transAnim);
}
/**
* 吸附屏幕方法,当view靠近四边小于设定的距离,且还在屏幕内时触发
**/
private void do_attach_boundary_effect(){
if (mNewPosition_left <= mAttach_Distance && mNewPosition_left > 0) { //左吸边
mNewPosition_left = 0;
mNewPosition_right = mNewPosition_left + getWidth();
return;
} else if (mScreenWidth > mNewPosition_right && mNewPosition_right > mScreenWidth - mAttach_Distance) { //右吸边
mNewPosition_right = mScreenWidth;
mNewPosition_left = mNewPosition_right - getWidth();
return;
}
if (mNewPosition_top <= mAttach_Distance && mNewPosition_top > 0) { //上边吸
mNewPosition_top = 0;
mNewPosition_bottom = mNewPosition_top + getHeight();
return;
} else if (mScreenHeight > mNewPosition_bottom && mNewPosition_bottom > mScreenHeight - mAttach_Distance) { //下边吸
mNewPosition_bottom = mScreenHeight;
mNewPosition_top = mNewPosition_bottom - getHeight();
return;
}
}
//刷新组件新位置UI
private void refreshNewPosition(int left, int top, int right,int bottom){
layout(left,top,right,bottom);
}
/**
* 像外提供监听click方法
* @param onClickListener
*/
public void setOnSpringMovingClickListener(OnClickListener onClickListener){
this.mOnClickListener = onClickListener;
}
/**
* 接口
*/
public interface OnClickListener {
void isClick(boolean isClick);
}
}
Android技术生活交流
微信 ----- qq群