这篇博客记录一下Android界面滑动切换的几种方式。
通过监听MotionEvent来进行滑动切换的原理,实际上是比较MotionEvent初始的坐标及移动后的坐标,
来判断用户是否进行了滑动的操作。
我们看看对应实现中最核心的代码:
public class FirstActivity extends AppCompatActivity {
..............
@Override
public boolean onTouchEvent(MotionEvent event) {
...........
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
//每次手指落下时,均记录初始横坐标
mOrigin = event.getX();
break;
case MotionEvent.ACTION_MOVE:
//手指移动时,记录移动后的横坐标
float move = event.getX();
.................
//当移动距离大于门限后,就可以跳转了
//这里实现左滑切换,因此要求orgin的横坐标 > move的横坐标
if ((mOrigin - move > MIN_MOVE_INSTANCE)
//此外,还可以判断移动速度是否大于门限
//由于手指移动时,会多次触发ACTION_MOVE,因此引入一个标志位,避免多次启动
&& (speed > SPEED_MIN) && !mAlreadyJump){
Log.d("ZJTest", "go to second activity");
startActivity(new Intent(this, SecondActivity.class));
.....................
mAlreadyJump = true;
}
break;
case MotionEvent.ACTION_UP:
..................
//手指离开屏幕后,重置mAlreadyJump
mAlreadyJump = false;
break;
default:
return super.onTouchEvent(event);
}
return true;
}
}
如果需要计算滑动速度,可以使用VelocityTracker。
其使用方法类似于:
...................
@Override
public boolean onTouchEvent(MotionEvent event) {
//让VelocityTracker监控event
addEventToVelocityTracker(event);
switch (event.getAction() & MotionEvent.ACTION_MASK) {
..........
case MotionEvent.ACTION_MOVE:
......
/获取event的速度
float speed = getScrollVelocity();
.....
break;
case MotionEvent.ACTION_UP:
//回收Velocity
recycleVelocityTracker();
.....
break;
.........
}
return true;
}
private VelocityTracker mVelocityTracker;
private void addEventToVelocityTracker(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
//利用addMovement监控event
mVelocityTracker.addMovement(event);
}
private void recycleVelocityTracker() {
//利用recycle接口回收VelocityTracker
mVelocityTracker.recycle();
mVelocityTracker = null;
}
private float getScrollVelocity() {
//利用computeCurrentVelocity计算速度,参数为单位时间
mVelocityTracker.computeCurrentVelocity(1000);
//getXVelocity用于获取水平方向的速度
return Math.abs(mVelocityTracker.getXVelocity());
}
.............
除了直接监听MotionEvent外,还可以利用GestureDetector完成类似的功能。
示例代码如下:
public class SecondActivity extends AppCompatActivity {
//这里使用的是兼容库中的GestureDetector
GestureDetectorCompat mGestureDetector;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
//与实现OnGestureListener的本地类配合使用
mGestureDetector = new GestureDetectorCompat(
this, new LocalGestureListener());
}
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//如上面源代码的注释,此处拦截MotionEvent给GestureDetector处理
mGestureDetector.onTouchEvent(ev);
return super.dispatchTouchEvent(ev);
}
.............
private class LocalGestureListener implements GestureDetector.OnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
//同样利用横坐标和移动速度,判断是否需要切换Activity、
//这里实现的是,右滑切换,因此要求后一个点的横坐标大于前一个点
float move = e2.getX() - e1.getX();
if (move > MIN_MOVE_INSTANCE && velocityX > SPEED_MIN) {
Log.d("ZJTest", "go to the first activity");
//由于FirstActivity启动的SecondActivity,因此SecondActivity finish后就回到了前一个Activity
finish();
................
}
return true;
}
}
}
为了整个切换工程更加平滑,可以在startActivity和finish后,利用overridePendingTransition接口实现一些动画效果。
上述demo的效果类似于下图:
demo代码下载地址
上述监听MotionEvent或者使用GestureListener均有些重复造轮子的感觉,
而且滑动效果不够强大。
例如,一旦滑动后,就会直接切换到另一个Activity;
不能做到:在切换的过程中展示下一个Activity,同时在切换过程中取消本次切换。
此时,就轮到ViewPager上场了。当利用ViewPager时,最好与Fragment组合使用。
我们将上面的FirstActivity和SecondActivity均转变为Fragment,如下:
public class FirstFragment extends Fragment {
public static FirstFragment newInstance() {
return new FirstFragment();
}
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState ) {
return inflater.inflate(R.layout.fragment_first, container, false);
}
}
public class SecondFragment extends Fragment {
public static SecondFragment newInstance() {
return new SecondFragment();
}
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState ) {
return inflater.inflate(R.layout.fragment_second, container, false);
}
}
然后,引入support-v4包后,利用Activity管理这两个Fragment:
Activity的布局中直接使用ViewPager:
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/view_pager"
android:layout_height="match_parent"
android:layout_width="match_parent"/>
其代码如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//定义数据
final Map data = new TreeMap<>();
data.put(0, FirstFragment.newInstance());
data.put(1, SecondFragment.newInstance());
//找到ViewPager
ViewPager viewPager = (ViewPager) findViewById(R.id.view_pager);
//为ViewPager配置Adapter
viewPager.setAdapter(new FragmentStatePagerAdapter(
getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
return data.get(position);
}
@Override
public int getCount() {
return data.size();
}
});
}
}
按照上述代码,就可以更简洁的实现滑动效果。
如下图所示,仍然可以有效滑动:
demo下载地址