Android 仿 窗帘效果 和 登录界面拖动效果 (Scroller类的应用) 附 2个DEMO及源码...

Android学习中,动作交互是软件中重要的一部分,其中的Scroller就是提供了拖动效果的类,在网上,比如说一些Launcher实现滑屏都可以通过这个类去实现。下面要说的就是上次Scroller类学习的后的实践了。

如果你还不了解Scroller类,那请先点击:Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)

了解之后再阅读以下内容,你会发现原来实现起来很简单。

之前说到过,在广泛使用的侧边滑动导航开源库 --SlidingLayer其实就是使用到了Scroller类进行的实现,(SlidingLayer下载地址:GITHUB ),而是这个库的实现过程中使用到的---Scroller类,我们可以使用这个库实现以下我要达到的效果,可是这样拿来就用,对于初学者提升不大,所以我决定直接去使用Scroller类去实现:
1)窗帘展开和关闭效果
2)登录界面拖动效果(有点类似PopupWindow,可是带上了拖拽效果)。

通过这2个例子,你就大概知道了Scroller类的基本使用情况,可以自己去写一些类似的效果了。

先上图,在上主要代码,最后上DEMO源码。

申明下:DEMO中的资源文件是在网上下载的2个应用中,发现效果不错和可以进一步完善(比如窗帘效果,原本是不带推拽效果),提取了应用的资源文件去自己实现的,目的是为了更好的达到展示效果。
代码中都带上了注释和说明,以便更好的了解实现过程。可能有的地方优化做的不足,望大家见谅。

效果图:

1)窗帘 效果
用途:可以使用于广告墙,公告栏等地方
说明:点击开关可以实现展开关闭功能,也可以通过推拽开关实现展开关闭效果,动画中加入了反弹效果,更加真实。
Android 仿 窗帘效果 和 登录界面拖动效果 (Scroller类的应用) 附 2个DEMO及源码...


2)登录窗体 效果
用途:可以使用在登录时候的登录方式选择,菜单选项等,有点类似于带拖拽效果的PopupWindow
说明:可以登录按钮展开关闭登录窗体,也可以通过推拽进行关闭。
注:这里的点击窗体之外消失是通过回调接口实现,这里没有列出,可以下载源码查看

Android 仿 窗帘效果 和 登录界面拖动效果 (Scroller类的应用) 附 2个DEMO及源码...

学习了Scroller类,大概的你也知道核心代码会是哪些内容,下面列举下

核心代码:

窗帘效果:

  1. publicclassCurtainViewextendsRelativeLayoutimplementsOnTouchListener{
  2. privatestaticStringTAG="CurtainView";
  3. privateContextmContext;
  4. /**Scroller拖动类*/
  5. privateScrollermScroller;
  6. /**屏幕高度*/
  7. privateintmScreenHeigh=0;
  8. /**屏幕宽度*/
  9. privateintmScreenWidth=0;
  10. /**点击时候Y的坐标*/
  11. privateintdownY=0;
  12. /**拖动时候Y的坐标*/
  13. privateintmoveY=0;
  14. /**拖动时候Y的方向距离*/
  15. privateintscrollY=0;
  16. /**松开时候Y的坐标*/
  17. privateintupY=0;
  18. /**广告幕布的高度*/
  19. privateintcurtainHeigh=0;
  20. /**是否打开*/
  21. privatebooleanisOpen=false;
  22. /**是否在动画*/
  23. privatebooleanisMove=false;
  24. /**绳子的图片*/
  25. privateImageViewimg_curtain_rope;
  26. /**广告的图片*/
  27. privateImageViewimg_curtain_ad;
  28. /**上升动画时间*/
  29. privateintupDuration=1000;
  30. /**下落动画时间*/
  31. privateintdownDuration=500;
  32. publicCurtainView(Contextcontext){
  33. super(context);
  34. init(context);
  35. }
  36. publicCurtainView(Contextcontext,AttributeSetattrs,intdefStyle){
  37. super(context,attrs,defStyle);
  38. init(context);
  39. }
  40. publicCurtainView(Contextcontext,AttributeSetattrs){
  41. super(context,attrs);
  42. init(context);
  43. }
  44. /**初始化*/
  45. privatevoidinit(Contextcontext){
  46. this.mContext=context;
  47. //Interpolator设置为有反弹效果的(Bounce:反弹)
  48. Interpolatorinterpolator=newBounceInterpolator();
  49. mScroller=newScroller(context,interpolator);
  50. mScreenHeigh=BaseTools.getWindowHeigh(context);
  51. mScreenWidth=BaseTools.getWindowWidth(context);
  52. //背景设置成透明
  53. this.setBackgroundColor(Color.argb(0,0,0,0));
  54. finalViewview=LayoutInflater.from(mContext).inflate(R.layout.curtain,null);
  55. img_curtain_ad=(ImageView)view.findViewById(R.id.img_curtain_ad);
  56. img_curtain_rope=(ImageView)view.findViewById(R.id.img_curtain_rope);
  57. addView(view);
  58. img_curtain_ad.post(newRunnable(){
  59. @Override
  60. publicvoidrun(){
  61. //TODOAuto-generatedmethodstub
  62. curtainHeigh=img_curtain_ad.getHeight();
  63. Log.d(TAG,"curtainHeigh="+curtainHeigh);
  64. CurtainView.this.scrollTo(0,curtainHeigh);
  65. //注意scrollBy和scrollTo的区别
  66. }
  67. });
  68. img_curtain_rope.setOnTouchListener(this);
  69. }
  70. /**
  71. *拖动动画
  72. *@paramstartY
  73. *@paramdy垂直距离,滚动的y距离
  74. *@paramduration时间
  75. */
  76. publicvoidstartMoveAnim(intstartY,intdy,intduration){
  77. isMove=true;
  78. mScroller.startScroll(0,startY,0,dy,duration);
  79. invalidate();//通知UI线程的更新
  80. }
  81. @Override
  82. protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){
  83. //TODOAuto-generatedmethodstub
  84. super.onLayout(changed,l,t,r,b);
  85. }
  86. @Override
  87. publicvoidcomputeScroll(){
  88. //判断是否还在滚动,还在滚动为true
  89. if(mScroller.computeScrollOffset()){
  90. scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
  91. //更新界面
  92. postInvalidate();
  93. isMove=true;
  94. }else{
  95. isMove=false;
  96. }
  97. super.computeScroll();
  98. }
  99. @Override
  100. publicbooleanonTouch(Viewv,MotionEventevent){
  101. //TODOAuto-generatedmethodstub
  102. if(!isMove){
  103. intoffViewY=0;//屏幕顶部和该布局顶部的距离
  104. switch(event.getAction()){
  105. caseMotionEvent.ACTION_DOWN:
  106. downY=(int)event.getRawY();
  107. offViewY=downY-(int)event.getX();
  108. returntrue;
  109. caseMotionEvent.ACTION_MOVE:
  110. moveY=(int)event.getRawY();
  111. scrollY=moveY-downY;
  112. if(scrollY<0){
  113. //向上滑动
  114. if(isOpen){
  115. if(Math.abs(scrollY)<=img_curtain_ad.getBottom()-offViewY){
  116. scrollTo(0,-scrollY);
  117. }
  118. }
  119. }else{
  120. //向下滑动
  121. if(!isOpen){
  122. if(scrollY<=curtainHeigh){
  123. scrollTo(0,curtainHeigh-scrollY);
  124. }
  125. }
  126. }
  127. break;
  128. caseMotionEvent.ACTION_UP:
  129. upY=(int)event.getRawY();
  130. if(Math.abs(upY-downY)<10){
  131. onRopeClick();
  132. break;
  133. }
  134. if(downY>upY){
  135. //向上滑动
  136. if(isOpen){
  137. if(Math.abs(scrollY)>curtainHeigh/2){
  138. //向上滑动超过半个屏幕高的时候开启向上消失动画
  139. startMoveAnim(this.getScrollY(),
  140. (curtainHeigh-this.getScrollY()),upDuration);
  141. isOpen=false;
  142. }else{
  143. startMoveAnim(this.getScrollY(),-this.getScrollY(),upDuration);
  144. isOpen=true;
  145. }
  146. }
  147. }else{
  148. //向下滑动
  149. if(scrollY>curtainHeigh/2){
  150. //向上滑动超过半个屏幕高的时候开启向上消失动画
  151. startMoveAnim(this.getScrollY(),-this.getScrollY(),upDuration);
  152. isOpen=true;
  153. }else{
  154. startMoveAnim(this.getScrollY(),(curtainHeigh-this.getScrollY()),upDuration);
  155. isOpen=false;
  156. }
  157. }
  158. break;
  159. default:
  160. break;
  161. }
  162. }
  163. returnfalse;
  164. }
  165. /**
  166. *点击绳索开关,会展开关闭
  167. *在onToch中使用这个中的方法来当点击事件,避免了点击时候响应onTouch的衔接不完美的影响
  168. */
  169. publicvoidonRopeClick(){
  170. if(isOpen){
  171. CurtainView.this.startMoveAnim(0,curtainHeigh,upDuration);
  172. }else{
  173. CurtainView.this.startMoveAnim(curtainHeigh,-curtainHeigh,downDuration);
  174. }
  175. isOpen=!isOpen;
  176. }
  177. }

登录界面:

  1. publicclassLoginViewextendsRelativeLayout{
  2. /**Scroller拖动类*/
  3. privateScrollermScroller;
  4. /**屏幕高度*/
  5. privateintmScreenHeigh=0;
  6. /**屏幕宽度*/
  7. privateintmScreenWidth=0;
  8. /**点击时候Y的坐标*/
  9. privateintdownY=0;
  10. /**拖动时候Y的坐标*/
  11. privateintmoveY=0;
  12. /**拖动时候Y的方向距离*/
  13. privateintscrollY=0;
  14. /**松开时候Y的坐标*/
  15. privateintupY=0;
  16. /**是否在移动*/
  17. privateBooleanisMoving=false;
  18. /**布局的高度*/
  19. privateintviewHeight=0;
  20. /**是否打开*/
  21. publicbooleanisShow=false;
  22. /**是否可以拖动*/
  23. publicbooleanmEnabled=true;
  24. /**点击外面是否关闭该界面*/
  25. publicbooleanmOutsideTouchable=true;
  26. /**上升动画时间*/
  27. privateintmDuration=800;
  28. privatefinalstaticStringTAG="LoginView";
  29. publicLoginView(Contextcontext){
  30. super(context);
  31. init(context);
  32. }
  33. publicLoginView(Contextcontext,AttributeSetattrs){
  34. super(context,attrs);
  35. init(context);
  36. }
  37. publicLoginView(Contextcontext,AttributeSetattrs,intdefStyle){
  38. super(context,attrs,defStyle);
  39. init(context);
  40. }
  41. privatevoidinit(Contextcontext){
  42. setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
  43. setFocusable(true);
  44. mScroller=newScroller(context);
  45. mScreenHeigh=BaseTools.getWindowHeigh(context);
  46. mScreenWidth=BaseTools.getWindowWidth(context);
  47. //背景设置成透明
  48. this.setBackgroundColor(Color.argb(0,0,0,0));
  49. finalViewview=LayoutInflater.from(context).inflate(R.layout.view_login,null);
  50. LayoutParamsparams=newLayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT);//如果不给他设这个,它的布局的MATCH_PARENT就不知道该是多少
  51. addView(view,params);//ViewGroup的大小,
  52. //背景设置成透明
  53. this.setBackgroundColor(Color.argb(0,0,0,0));
  54. view.post(newRunnable(){
  55. @Override
  56. publicvoidrun(){
  57. //TODOAuto-generatedmethodstub
  58. viewHeight=view.getHeight();
  59. }
  60. });
  61. LoginView.this.scrollTo(0,mScreenHeigh);
  62. ImageViewbtn_close=(ImageView)view.findViewById(R.id.btn_close);
  63. btn_close.setOnClickListener(newOnClickListener(){
  64. @Override
  65. publicvoidonClick(Viewv){
  66. //TODOAuto-generatedmethodstub
  67. dismiss();
  68. }
  69. });
  70. }
  71. @Override
  72. publicbooleanonInterceptTouchEvent(MotionEventev){
  73. if(!mEnabled){
  74. returnfalse;
  75. }
  76. returnsuper.onInterceptTouchEvent(ev);
  77. }
  78. @Override
  79. publicbooleanonTouchEvent(MotionEventevent){
  80. //TODOAuto-generatedmethodstub
  81. switch(event.getAction()){
  82. caseMotionEvent.ACTION_DOWN:
  83. downY=(int)event.getY();
  84. Log.d(TAG,"downY="+downY);
  85. //如果完全显示的时候,让布局得到触摸监听,如果不显示,触摸事件不拦截,向下传递
  86. if(isShow){
  87. returntrue;
  88. }
  89. break;
  90. caseMotionEvent.ACTION_MOVE:
  91. moveY=(int)event.getY();
  92. scrollY=moveY-downY;
  93. //向下滑动
  94. if(scrollY>0){
  95. if(isShow){
  96. scrollTo(0,-Math.abs(scrollY));
  97. }
  98. }else{
  99. if(mScreenHeigh-this.getTop()<=viewHeight&&!isShow){
  100. scrollTo(0,Math.abs(viewHeight-scrollY));
  101. }
  102. }
  103. break;
  104. caseMotionEvent.ACTION_UP:
  105. upY=(int)event.getY();
  106. if(isShow){
  107. if(this.getScrollY()<=-(viewHeight/2)){
  108. startMoveAnim(this.getScrollY(),-(viewHeight-this.getScrollY()),mDuration);
  109. isShow=false;
  110. Log.d("isShow","false");
  111. }else{
  112. startMoveAnim(this.getScrollY(),-this.getScrollY(),mDuration);
  113. isShow=true;
  114. Log.d("isShow","true");
  115. }
  116. }
  117. Log.d("this.getScrollY()",""+this.getScrollY());
  118. changed();
  119. break;
  120. caseMotionEvent.ACTION_OUTSIDE:
  121. Log.d(TAG,"ACTION_OUTSIDE");
  122. break;
  123. default:
  124. break;
  125. }
  126. returnsuper.onTouchEvent(event);
  127. }
  128. /**
  129. *拖动动画
  130. *@paramstartY
  131. *@paramdy移动到某点的Y坐标距离
  132. *@paramduration时间
  133. */
  134. publicvoidstartMoveAnim(intstartY,intdy,intduration){
  135. isMoving=true;
  136. mScroller.startScroll(0,startY,0,dy,duration);
  137. invalidate();//通知UI线程的更新
  138. }
  139. @Override
  140. publicvoidcomputeScroll(){
  141. if(mScroller.computeScrollOffset()){
  142. scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
  143. //更新界面
  144. postInvalidate();
  145. isMoving=true;
  146. }else{
  147. isMoving=false;
  148. }
  149. super.computeScroll();
  150. }
  151. /**开打界面*/
  152. publicvoidshow(){
  153. if(!isShow&&!isMoving){
  154. LoginView.this.startMoveAnim(-viewHeight,viewHeight,mDuration);
  155. isShow=true;
  156. Log.d("isShow","true");
  157. changed();
  158. }
  159. }
  160. /**关闭界面*/
  161. publicvoiddismiss(){
  162. if(isShow&&!isMoving){
  163. LoginView.this.startMoveAnim(0,-viewHeight,mDuration);
  164. isShow=false;
  165. Log.d("isShow","false");
  166. changed();
  167. }
  168. }
  169. /**是否打开*/
  170. publicbooleanisShow(){
  171. returnisShow;
  172. }
  173. /**获取是否可以拖动*/
  174. publicbooleanisSlidingEnabled(){
  175. returnmEnabled;
  176. }
  177. /**设置是否可以拖动*/
  178. publicvoidsetSlidingEnabled(booleanenabled){
  179. mEnabled=enabled;
  180. }
  181. /**
  182. *设置监听接口,实现遮罩层效果
  183. */
  184. publicvoidsetOnStatusListener(onStatusListenerlistener){
  185. this.statusListener=listener;
  186. }
  187. publicvoidsetOutsideTouchable(booleantouchable){
  188. mOutsideTouchable=touchable;
  189. }
  190. /**
  191. *显示状态发生改变时候执行回调借口
  192. */
  193. publicvoidchanged(){
  194. if(statusListener!=null){
  195. if(isShow){
  196. statusListener.onShow();
  197. }else{
  198. statusListener.onDismiss();
  199. }
  200. }
  201. }
  202. /**监听接口*/
  203. publiconStatusListenerstatusListener;
  204. /**
  205. *监听接口,来在主界面监听界面变化状态
  206. */
  207. publicinterfaceonStatusListener{
  208. /**开打状态*/
  209. publicvoidonShow();
  210. /**关闭状态*/
  211. publicvoidonDismiss();
  212. }
  213. @Override
  214. protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){
  215. //TODOAuto-generatedmethodstub
  216. super.onLayout(changed,l,t,r,b);
  217. }
  218. }

其实代码大同小异,了解后你就可以举一反三,去自己的VIEW中实现自己想要的效果。


最后,上源码:下载地址