按照国际惯例咱们先看效果图
GayHub传送门: github.com/workdawn/Dr…
相关特性
支持大于两个子页面
支持竖向和横向两个方向的拖拉
支持弹性拖拉
支持常见的ListView、ScrollView、HorizontalScrollView、NestedScrollView、RecyclerView、ViewPager、WebView等控件的组合使用
支持页面进入监听、支持滚动监听(仅监听第一个页面)、支持拖拽监听
支持根据id跳转到指定页面
更多特性和相关使用细节请到项目使用页面查看
项目简单介绍
原理
DragToDetail继承自LinearLayout,通过覆写LinearLayout的三个相关触摸事件来达到内部多个子控件的拖动配合。在拖动过程中最重要的一点就是对控件可滑动与否的判断,好在Android在v4包中给我们提供了ViewCompat.canScrollVertically(target, direction)
和ViewCompat.canScrollHorizontally(target, direction)
这两个方法,第一个方法是用来判断控件是否可以在垂直方向滚动的,有两个参数target和direction,其中target表示当前滑动view,direction表示可以滑动的方向,负数表示是否可以向下滑动,正数表示是否可以向上滑动。第二个方法用来判断控件是否可以再水平方向继续滚动,参数和第一个方法的意思相同,这里就不再多多叙述了,读者可以自行查看相关源码。DragToDetail中有关是否可以继续在垂直方向滑动的代码片段如下:
/**
* 判断控件是否可以在垂直方向滚动
* @param target 控件
* @param direction 滑动方向,正值为向上,负值为向下
* @param event 事件
* @return 能否滚动
*/
private boolean canScrollVertically(View target, int direction, MotionEvent event){
if(currentTargetView == null) return false;
if(target instanceof ViewPager){
ViewPager viewPager = (ViewPager) target;
View currentPagerView = null;
if(viewPager.getAdapter() instanceof DragFragmentPagerAdapter){
currentPagerView = ((DragFragmentPagerAdapter) viewPager.getAdapter()).getCurrentView();
} else if(viewPager.getAdapter() instanceof DragFragmentStatePagerAdapter){
currentPagerView = ((DragFragmentStatePagerAdapter) viewPager.getAdapter()).getCurrentView();
}
return currentPagerView != null && canScrollVertically(currentPagerView, direction, event);
} else if ((target instanceof AbsListView) ||
(target instanceof RecyclerView) ||
(target instanceof ScrollView) ||
(target instanceof NestedScrollView) ||
(target instanceof WebView)){
return ViewCompat.canScrollVertically(target, direction);
} else {
if((target instanceof ViewGroup) && ((ViewGroup) target).getChildCount() > 0){
ViewGroup vg = (ViewGroup) target;
for (int i = 0; i < vg.getChildCount(); i++) {
View v = vg.getChildAt(i);
if(checkTouchRange(v, event)){
return canScrollVertically(v, direction, event);
}
}
} else {
return ViewCompat.canScrollVertically(target, direction);
}
}
return false;
}
复制代码
水平方向的判断读者可以自行下载源码查看,其实原理一样,唯一的区别就是当target控件为ViewPager的时候,水平方向的判断依据是ViewPager是否滑动到最后一页面,垂直方向的判断需要根据Pager内部View来决定。
有关弹性滑动当然就是使用Scroller来实现了,在滑动过程中会持续监听手指滑动距离,当滑动距离大于设置的临界距离时,放开手指触发滑动到下一个页面,如果滑动距离小于设置的临界距离则直接回弹。临界距离可以通过属性reboundPercent
来设置,这是一个float类型的数值,用来表示一个百分比,DragToDetail中有关弹性滑动代码片段如下(这里只截取垂直方向的处理):
case VERTICAL:
startY = getScrollY();
float yVelocity = mVelocityTracker.getYVelocity();
int currentTargetViewHeight = currentTargetView.getMeasuredHeight();
float baseHeight = currentTargetViewHeight * mReboundPercent;
int accumulateHeight = accumulateViewHeight();
int difference = startY - accumulateHeight;
if(Math.abs(yVelocity) >= mMaxFlingVelocity){
if(yVelocity > 0){
//上一页
if(startY <= currentTargetViewHeight){
dy = -startY;
} else {
dy = -startY + currentTargetViewHeight;
}
changeCurrentTargetIndexAndView(false);
} else {
//下一页
dy = (accumulateHeight + currentTargetViewHeight) - startY;
changeCurrentTargetIndexAndView(true);
}
} else {
if(Math.abs(difference) >= baseHeight){
//跳转到另外一个页面
if(difference < 0){
//上一页
dy = -(currentTargetViewHeight - Math.abs(difference));
changeCurrentTargetIndexAndView(false);
} else {
//下一页
dy = currentTargetViewHeight - Math.abs(difference);
changeCurrentTargetIndexAndView(true);
}
} else {
//回弹
dy = -difference;
}
}
break;
复制代码
DragToDetail中有关跳转到指定页面的原理,其实就是通过调用Scroller的startScroll,通过跳转的页面id计算出需要滑动的距离和跳转的方向,这里有一个需要注意的地方,从前向后跳的时候需要处理下中间跨度页面的滚动问题,因为如果不处理中间页面的滚动,那么跳转完页面后在回来,前面得页面可能无法滑动到底部,只能看到上半部分。这就是源码中之所以需要调用processEnd(View v)方法的原因。
最后,其实说到最后来看这个控件并没有什么偏避的知识点,都是一些对滑动事件和滚动监听的处理,这些东西平时在工作中多注意下,应该都不是问题,如果想要了解更多内容可以自行下载源码查看哦。。。
再来一遍GayHub地址:github.com/workdawn/Dr…
喜欢的话给个star吧!!!
相关参考: