在android学习中,动作交互是软件中重要的一部分,其中的Scroller就是提供了拖动效果的类,在网上,比如说一些Launcher实现滑屏都可以通过这个类去实现。下面要说的就是上次Scroller类学习的后的实践了。
如果你还不了解Scroller类,那请先点击:Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)
了解之后再阅读以下内容,你会发现原来实现起来很简单。
之前说到过,在广泛使用的侧边滑动导航开源库 --SlidingLayer其实就是使用到了Scroller类进行的实现,(SlidingLayer下载地址:GITHUB ),而是这个库的实现过程中使用到的---Scroller类,我们可以使用这个库实现以下我要达到的效果,可是这样拿来就用,对于初学者提升不大,所以我决定直接去使用Scroller类去实现:
1)窗帘展开和关闭效果
2)登录界面拖动效果(有点类似PopupWindow,可是带上了拖拽效果)。
通过这2个例子,你就大概知道了Scroller类的基本使用情况,可以自己去写一些类似的效果了。
先上图,在上主要代码,最后上DEMO源码。
申明下:DEMO中的资源文件是在网上下载的2个应用中,发现效果不错和可以进一步完善(比如窗帘效果,原本是不带推拽效果),提取了应用的资源文件去自己实现的,目的是为了更好的达到展示效果。
代码中都带上了注释和说明,以便更好的了解实现过程。可能有的地方优化做的不足,望大家见谅。
效果图:
1)窗帘 效果
用途:可以使用于广告墙,公告栏等地方
说明:点击开关可以实现展开关闭功能,也可以通过推拽开关实现展开关闭效果,动画中加入了反弹效果,更加真实。
2)登录窗体 效果
用途:可以使用在登录时候的登录方式选择,菜单选项等,有点类似于带拖拽效果的PopupWindow
说明:可以登录按钮展开关闭登录窗体,也可以通过推拽进行关闭。
注:这里的点击窗体之外消失是通过回调接口实现,这里没有列出,可以下载源码查看
学习了Scroller类,大概的你也知道核心代码会是哪些内容,下面列举下
核心代码:
窗帘效果:
public class CurtainView extends RelativeLayout implements OnTouchListener{ private static String TAG = "CurtainView"; private Context mContext; /** Scroller 拖动类 */ private Scroller mScroller; /** 屏幕高度 */ private int mScreenHeigh = 0; /** 屏幕宽度 */ private int mScreenWidth = 0; /** 点击时候Y的坐标*/ private int downY = 0; /** 拖动时候Y的坐标*/ private int moveY = 0; /** 拖动时候Y的方向距离*/ private int scrollY = 0; /** 松开时候Y的坐标*/ private int upY = 0; /** 广告幕布的高度*/ private int curtainHeigh = 0; /** 是否 打开*/ private boolean isOpen = false; /** 是否在动画 */ private boolean isMove = false; /** 绳子的图片*/ private ImageView img_curtain_rope; /** 广告的图片*/ private ImageView img_curtain_ad; /** 上升动画时间 */ private int upDuration = 1000; /** 下落动画时间 */ private int downDuration = 500; public CurtainView(Context context) { super(context); init(context); } public CurtainView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } public CurtainView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } /** 初始化 */ private void init(Context context) { this.mContext = context; //Interpolator 设置为有反弹效果的 (Bounce:反弹) Interpolator interpolator = new BounceInterpolator(); mScroller = new Scroller(context, interpolator); mScreenHeigh = BaseTools.getWindowHeigh(context); mScreenWidth = BaseTools.getWindowWidth(context); // 背景设置成透明 this.setBackgroundColor(Color.argb(0, 0, 0, 0)); final View view = LayoutInflater.from(mContext).inflate(R.layout.curtain, null); img_curtain_ad = (ImageView)view.findViewById(R.id.img_curtain_ad); img_curtain_rope = (ImageView)view.findViewById(R.id.img_curtain_rope); addView(view); img_curtain_ad.post(new Runnable() { @Override public void run() { // TODO Auto-generated method stub curtainHeigh = img_curtain_ad.getHeight(); Log.d(TAG, "curtainHeigh= " + curtainHeigh); CurtainView.this.scrollTo(0, curtainHeigh); //注意scrollBy和scrollTo的区别 } }); img_curtain_rope.setOnTouchListener(this); } /** * 拖动动画 * @param startY * @param dy 垂直距离, 滚动的y距离 * @param duration 时间 */ public void startMoveAnim(int startY, int dy, int duration) { isMove = true; mScroller.startScroll(0, startY, 0, dy, duration); invalidate();//通知UI线程的更新 } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // TODO Auto-generated method stub super.onLayout(changed, l, t, r, b); } @Override public void computeScroll() { //判断是否还在滚动,还在滚动为true if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); //更新界面 postInvalidate(); isMove = true; } else { isMove = false; } super.computeScroll(); } @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub if (!isMove) { int offViewY = 0;//屏幕顶部和该布局顶部的距离 switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downY = (int) event.getRawY(); offViewY = downY - (int)event.getX(); return true; case MotionEvent.ACTION_MOVE: moveY = (int) event.getRawY(); scrollY = moveY - downY; if (scrollY < 0) { // 向上滑动 if(isOpen){ if(Math.abs(scrollY) <= img_curtain_ad.getBottom() - offViewY){ scrollTo(0, -scrollY); } } } else { // 向下滑动 if(!isOpen){ if (scrollY <= curtainHeigh) { scrollTo(0, curtainHeigh - scrollY); } } } break; case MotionEvent.ACTION_UP: upY = (int) event.getRawY(); if(Math.abs(upY - downY) < 10){ onRopeClick(); break; } if (downY > upY) { // 向上滑动 if(isOpen){ if (Math.abs(scrollY) > curtainHeigh / 2) { // 向上滑动超过半个屏幕高的时候 开启向上消失动画 startMoveAnim(this.getScrollY(), (curtainHeigh - this.getScrollY()), upDuration); isOpen = false; } else { startMoveAnim(this.getScrollY(), -this.getScrollY(),upDuration); isOpen = true; } } } else { // 向下滑动 if (scrollY > curtainHeigh / 2) { // 向上滑动超过半个屏幕高的时候 开启向上消失动画 startMoveAnim(this.getScrollY(), -this.getScrollY(),upDuration); isOpen = true; } else { startMoveAnim(this.getScrollY(),(curtainHeigh - this.getScrollY()), upDuration); isOpen = false; } } break; default: break; } } return false; } /** * 点击绳索开关,会展开关闭 * 在onToch中使用这个中的方法来当点击事件,避免了点击时候响应onTouch的衔接不完美的影响 */ public void onRopeClick(){ if(isOpen){ CurtainView.this.startMoveAnim(0, curtainHeigh, upDuration); }else{ CurtainView.this.startMoveAnim(curtainHeigh,-curtainHeigh, downDuration); } isOpen = !isOpen; }}
登录界面:
public class LoginView extends RelativeLayout { /** Scroller 拖动类 */ private Scroller mScroller; /** 屏幕高度 */ private int mScreenHeigh = 0; /** 屏幕宽度 */ private int mScreenWidth = 0; /** 点击时候Y的坐标*/ private int downY = 0; /** 拖动时候Y的坐标*/ private int moveY = 0; /** 拖动时候Y的方向距离*/ private int scrollY = 0; /** 松开时候Y的坐标*/ private int upY = 0; /** 是否在移动*/ private Boolean isMoving = false; /** 布局的高度*/ private int viewHeight = 0; /** 是否打开*/ public boolean isShow = false; /** 是否可以拖动*/ public boolean mEnabled = true; /** 点击外面是否关闭该界面*/ public boolean mOutsideTouchable = true; /** 上升动画时间 */ private int mDuration = 800; private final static String TAG = "LoginView"; public LoginView(Context context) { super(context); init(context); } public LoginView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public LoginView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context) { setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); setFocusable(true); mScroller = new Scroller(context); mScreenHeigh = BaseTools.getWindowHeigh(context); mScreenWidth = BaseTools.getWindowWidth(context); // 背景设置成透明 this.setBackgroundColor(Color.argb(0, 0, 0, 0)); final View view = LayoutInflater.from(context).inflate(R.layout.view_login,null); LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT);// 如果不给他设这个,它的布局的MATCH_PARENT就不知道该是多少 addView(view, params);// ViewGroup的大小, // 背景设置成透明 this.setBackgroundColor(Color.argb(0, 0, 0, 0)); view.post(new Runnable() { @Override public void run() { // TODO Auto-generated method stub viewHeight = view.getHeight(); } }); LoginView.this.scrollTo(0, mScreenHeigh); ImageView btn_close = (ImageView)view.findViewById(R.id.btn_close); btn_close.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub dismiss(); } }); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if(!mEnabled){ return false; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downY = (int) event.getY(); Log.d(TAG, "downY = " + downY); //如果完全显示的时候,让布局得到触摸监听,如果不显示,触摸事件不拦截,向下传递 if(isShow){ return true; } break; case MotionEvent.ACTION_MOVE: moveY = (int) event.getY(); scrollY = moveY - downY; //向下滑动 if (scrollY > 0) { if(isShow){ scrollTo(0, -Math.abs(scrollY)); } }else{ if(mScreenHeigh - this.getTop() <= viewHeight && !isShow){ scrollTo(0, Math.abs(viewHeight - scrollY)); } } break; case MotionEvent.ACTION_UP: upY = (int) event.getY(); if(isShow){ if( this.getScrollY() <= -(viewHeight /2)){ startMoveAnim(this.getScrollY(),-(viewHeight - this.getScrollY()), mDuration); isShow = false; Log.d("isShow", "false"); } else { startMoveAnim(this.getScrollY(), -this.getScrollY(), mDuration); isShow = true; Log.d("isShow", "true"); } } Log.d("this.getScrollY()", ""+this.getScrollY()); changed(); break; case MotionEvent.ACTION_OUTSIDE: Log.d(TAG, "ACTION_OUTSIDE"); break; default: break; } return super.onTouchEvent(event); } /** * 拖动动画 * @param startY * @param dy 移动到某点的Y坐标距离 * @param duration 时间 */ public void startMoveAnim(int startY, int dy, int duration) { isMoving = true; mScroller.startScroll(0, startY, 0, dy, duration); invalidate();//通知UI线程的更新 } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); // 更新界面 postInvalidate(); isMoving = true; } else { isMoving = false; } super.computeScroll(); } /** 开打界面 */ public void show(){ if(!isShow && !isMoving){ LoginView.this.startMoveAnim(-viewHeight, viewHeight, mDuration); isShow = true; Log.d("isShow", "true"); changed(); } } /** 关闭界面 */ public void dismiss(){ if(isShow && !isMoving){ LoginView.this.startMoveAnim(0, -viewHeight, mDuration); isShow = false; Log.d("isShow", "false"); changed(); } } /** 是否打开 */ public boolean isShow(){ return isShow; } /** 获取是否可以拖动*/ public boolean isSlidingEnabled() { return mEnabled; } /** 设置是否可以拖动*/ public void setSlidingEnabled(boolean enabled) { mEnabled = enabled; } /** * 设置监听接口,实现遮罩层效果 */ public void setOnStatusListener(onStatusListener listener){ this.statusListener = listener; } public void setOutsideTouchable(boolean touchable) { mOutsideTouchable = touchable; } /** * 显示状态发生改变时候执行回调借口 */ public void changed(){ if(statusListener != null){ if(isShow){ statusListener.onShow(); }else{ statusListener.onDismiss(); } } } /** 监听接口*/ public onStatusListener statusListener; /** * 监听接口,来在主界面监听界面变化状态 */ public interface onStatusListener{ /** 开打状态 */ public void onShow(); /** 关闭状态 */ public void onDismiss(); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // TODO Auto-generated method stub super.onLayout(changed, l, t, r, b); }}
其实代码大同小异,了解后你就可以举一反三,去自己的VIEW中实现自己想要的效果。
最后,上源码:下载地址
- 2楼zongqiangchen33分钟前
- 非常棒 谢谢楼主分享
- 1楼w7758258weng昨天 23:00
- 有点意思,多谢楼主