更新说明:
1、在QQ网友北京-旭的提醒下,在SlideFrame的initilize方法中添加了focusable、focusableInTouch、clickable的状态设置,否则会导致部分情况下无法滑动,感谢!
一、效果动图
二、使用说明
使用方法很简单,只有一个类HorizontalActivity,继承自FragmentActivity类,实现了contentView的滑动事件触发和动画效果,要在自己的代码里实现,方法两种:
1、如果对Activity没特殊要求,直接继承HorizontalActivity即可
2、如果Activity的父类必须是某一特定类型的Activity子类,则可以仿照我的写法对该类进行继承
三、HorizontalActivity类的代码
1 package com.beifeng.widget; 2 3 import android.content.Context; 4 import android.support.v4.app.FragmentActivity; 5 import android.util.AttributeSet; 6 import android.view.LayoutInflater; 7 import android.view.MotionEvent; 8 import android.view.View; 9 import android.view.ViewGroup.LayoutParams; 10 import android.view.animation.Animation; 11 import android.view.animation.Animation.AnimationListener; 12 import android.view.animation.DecelerateInterpolator; 13 import android.view.animation.Transformation; 14 import android.widget.FrameLayout; 15 16 /** 17 * HorizontalActivity:可滑动Activity 18 * 19 * 注意事项: 本Activity中与滑动方向相同的滑动操作会被拦截 20 * 21 * @author HalfmanG2 22 * @version 1.0.0 23 * @since JDK7 SDK19 24 */ 25 public class HorizontalActivity extends FragmentActivity { 26 27 /** 框架视图 */ 28 protected SlideFrame frameView; 29 /** 内容视图 */ 30 protected View contentView; 31 32 @Override 33 public void setContentView(int layoutResID) { 34 // 初始化frame 35 if (frameView == null) { 36 // 未初始化则初始化 37 frameView = new SlideFrame(this); 38 } else { 39 // 已经初始化则清空 40 frameView.removeAllViews(); 41 } 42 // 创造framelayout的填充参数 43 FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(-1, -1); 44 // 获取layoutResId对应的contentView视图并插入frameView 45 LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); 46 contentView = inflater.inflate(layoutResID, null); 47 frameView.addView(contentView, params); 48 // 设置frameview为根视图 49 super.setContentView(frameView); 50 } 51 52 @Override 53 public void setContentView(View view) { 54 // 初始化frame 55 if (frameView == null) { 56 // 未初始化则初始化 57 frameView = new SlideFrame(this); 58 } else { 59 // 已经初始化则清空 60 frameView.removeAllViews(); 61 } 62 // 创造framelayout的填充参数 63 FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(-1, -1); 64 // 获取view为contentView视图并插入frameView 65 contentView = view; 66 frameView.addView(contentView, params); 67 // 设置frameview为根视图 68 super.setContentView(frameView); 69 } 70 71 @Override 72 public void setContentView(View view, LayoutParams params) { 73 // 初始化frame 74 if (frameView == null) { 75 // 未初始化则初始化 76 frameView = new SlideFrame(this); 77 } else { 78 // 已经初始化则清空 79 frameView.removeAllViews(); 80 } 81 // 创造framelayout的填充参数 82 FrameLayout.LayoutParams fp = new FrameLayout.LayoutParams(-1, -1); 83 // 获取view为contentView视图并插入frameView 84 contentView = view; 85 frameView.addView(contentView, fp); 86 // 设置frameview为根视图 87 super.setContentView(frameView, params); 88 } 89 90 /** 91 * 推出页面 92 */ 93 protected void onSlideFinish() { 94 finish(); 95 } 96 97 /** 98 * 位移内容视图到 99 * 100 * @param position 101 * 目标位置 102 */ 103 public void slideTo(int position) { 104 if (android.os.Build.VERSION.SDK_INT > 10) { 105 contentView.setX(position); 106 } else { 107 android.widget.FrameLayout.LayoutParams params = (android.widget.FrameLayout.LayoutParams) contentView 108 .getLayoutParams(); 109 params.setMargins(position, 0, -position, 0); 110 contentView.setLayoutParams(params); 111 } 112 } 113 114 /** 115 * 获得当前容器位移 116 * 117 * @return 当前容器位移 118 */ 119 public int getSlide() { 120 if (android.os.Build.VERSION.SDK_INT > 10) { 121 return (int) contentView.getX(); 122 } else { 123 return ((android.widget.FrameLayout.LayoutParams) contentView 124 .getLayoutParams()).leftMargin; 125 } 126 } 127 128 /** 129 * 滑动框架 130 * 131 * @author HalfmanG2 132 * @version 1.0.0 133 * @since JDK7 SDK19 134 */ 135 public class SlideFrame extends FrameLayout { 136 /** 默认滑动阀值 */ 137 private final static int DEFAULT_SLIDE_DUMPING = 8; 138 /** 默认状态改变阀值 */ 139 private final static int DEFAULT_DO_DUMPING = 100; 140 /** 滑动起始位置与当前位置 */ 141 private int startX, currentX, startY, currentY; 142 /** 是否拦截事件,是否已经完成滑动检查 */ 143 private boolean doNotIntercept, hasChecked; 144 /** 滑动阀值 */ 145 private int slideDumping; 146 /** 操作阀值 */ 147 private int doDumping; 148 /** 滑屏动画 */ 149 protected SlideAnimation slideAnimation; 150 151 @Override 152 public boolean onInterceptTouchEvent(MotionEvent ev) { 153 super.onInterceptTouchEvent(ev); 154 // 若当前处在侧滑状态中,则拦截信号 155 if ((!doNotIntercept) && hasChecked) { 156 return true; 157 } 158 // 否则使用默认 159 return false; 160 } 161 162 @Override 163 public boolean dispatchTouchEvent(MotionEvent ev) { 164 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 165 // 获得起始滑动坐标 166 startX = (int) ev.getX(); 167 startY = (int) ev.getY(); 168 // 初始化状态 169 doNotIntercept = false; 170 hasChecked = false; 171 } else if (!doNotIntercept) { 172 // 获得当前滑动坐标 173 currentX = (int) ev.getX(); 174 currentY = (int) ev.getY(); 175 // 根据滑动类型区分 176 switch (ev.getAction()) { 177 case MotionEvent.ACTION_MOVE: // 移动状态 178 if (hasChecked) { 179 doSlide(); 180 } else { 181 doCheck(); 182 } 183 break; 184 case MotionEvent.ACTION_CANCEL: // 取消状态 185 case MotionEvent.ACTION_UP: // 抬起状态 186 // 初始化状态 187 doNotIntercept = false; 188 hasChecked = false; 189 if (Math.abs(currentX - startX) > doDumping) { 190 if (currentX > startX) { 191 // 右滑 192 slideAnimation = new SlideAnimation(getSlide(), 193 contentView.getWidth(), 0); 194 slideAnimation 195 .setAnimationListener(new AnimationListener() { 196 @Override 197 public void onAnimationStart( 198 Animation animation) { 199 } 200 201 @Override 202 public void onAnimationRepeat( 203 Animation animation) { 204 } 205 206 @Override 207 public void onAnimationEnd( 208 Animation animation) { 209 onSlideFinish(); 210 } 211 }); 212 startAnimation(slideAnimation); 213 } 214 } else { 215 // 返回0位置 216 slideAnimation = new SlideAnimation(getSlide(), 0, 0); 217 startAnimation(slideAnimation); 218 } 219 break; 220 default: 221 break; 222 } 223 } 224 return super.dispatchTouchEvent(ev); 225 } 226 227 /** 228 * 检查是否超过滑动阀值开启滑动状态 229 */ 230 private void doCheck() { 231 if (Math.abs(startY - currentY) > slideDumping) { 232 hasChecked = true; 233 doNotIntercept = true; 234 slideTo(0); 235 } else if (currentX - startX > slideDumping) { 236 hasChecked = true; 237 doNotIntercept = false; 238 } 239 } 240 241 /** 242 * 进行滑动 243 */ 244 private void doSlide() { 245 if (currentX > startX) { 246 slideTo(currentX - startX); 247 } else { 248 slideTo(0); 249 } 250 } 251 252 /** 253 * 设置滑动阀值 254 * 255 * @param dpValue 256 */ 257 public void setSlideDumping(int dpValue) { 258 slideDumping = dip2px(dpValue); 259 } 260 261 /** 262 * 设置状态改变阀值 263 * 264 * @param dpValue 265 */ 266 public void setDoDumping(int dpValue) { 267 doDumping = dip2px(dpValue); 268 } 269 270 /** 271 * 二级构造方法 272 */ 273 private void initilize() { 274 setSlideDumping(DEFAULT_SLIDE_DUMPING); 275 setDoDumping(DEFAULT_DO_DUMPING); 276 doNotIntercept = false; 277 hasChecked = false; 278 setClickable(true); 279 setFocusable(true); 280 setFocusableInTouchMode(true); 281 } 282 283 /** 284 * 构造方法 285 * 286 * @param context 287 * @param attrs 288 * @param defStyle 289 */ 290 public SlideFrame(Context context, AttributeSet attrs, int defStyle) { 291 super(context, attrs, defStyle); 292 initilize(); 293 } 294 295 /** 296 * 构造方法 297 * 298 * @param context 299 * @param attrs 300 */ 301 public SlideFrame(Context context, AttributeSet attrs) { 302 super(context, attrs); 303 initilize(); 304 } 305 306 /** 307 * 构造方法 308 * 309 * @param context 310 */ 311 public SlideFrame(Context context) { 312 super(context); 313 initilize(); 314 } 315 316 /** 317 * 讲dip值转换为px值,像素密度距离转像素距离 318 * 319 * @param dipValue dp值 320 * @return px值 321 */ 322 private int dip2px(float dipValue) { 323 // 获得像素密度 324 final float scale = getContext().getResources().getDisplayMetrics().density; 325 // 四舍五入dp值乘像素密度 326 return (int) (dipValue * scale + 0.5f); 327 } 328 } 329 330 /** 331 * 滑动动画类 332 * 333 * @author HalfmanG2 334 */ 335 public class SlideAnimation extends Animation { 336 /** 起始位置,目标位置 */ 337 private float from, to; 338 /** 339 * 构造方法 340 * @param from 起始位置 341 * @param to 目标位置 342 * @param startOffset 起始延迟 343 */ 344 public SlideAnimation(int from, int to, int startOffset) { 345 this.from = from; 346 this.to = to; 347 setFillEnabled(false); 348 setDuration(200); 349 setRepeatCount(0); 350 setStartOffset(startOffset); 351 setInterpolator(new DecelerateInterpolator()); 352 } 353 @Override 354 protected void applyTransformation(float interpolatedTime, 355 Transformation t) { 356 float current = from + (to - from) * interpolatedTime; 357 slideTo((int) current); 358 super.applyTransformation(interpolatedTime, t); 359 } 360 } 361 }
四、使用详细步骤
1、建立一个Android工程,项目最小api level必须在api level 11及以上
2、把本类考入任意代码包下
3、项目中想要实现Activity滑动退出效果的Activity继承本类
4、如果要在滑动过程中显示上一个Activity的话,将Activity的背景设置为透明,方法建议通过设置Activity的Style或者说theme来实现
5、如果滑动过程中需要加入一些特殊效果,可以复写slideTo(int)方法,记得别把super.slideTo(int)给漏了,否则滑动失效
6、如果滑动结束后不希望立即返回上一页,可以复写onSlideFinish()方法
PS、
很简单的实现方法,但离我觉得完美还太远,所以如果有更好的实现方法希望可以一起交流下:
联系方式QQ:811868948,备注加上Android交流,有好的想法一定要联系我,谢谢!
联系方式E-Mail:halfmanhuang@gmail.com