效果图镇楼
如果我们想要完成上图所产生的效果,必须深层次的解决ScrollView嵌套ListView所产生的的滑动冲突,解决滑动冲突的最好办法就是重写底层布局的滑动监听事件,有兴趣的同学可以去看一下android开发艺术探索,对滑动冲突的解决写的比较全面。这里我们就不多赘述,五万在这里将自己重写好的底层布局分享给大家,大家直接拿去用就好了。不用谢我,点赞就好。
一,讲五万写好布局文件和适配器粘贴到你的项目中
1.DragSrollLayout(重新改写监听后的View父布局)——直接粘就行
public class DragScrollLayout extends LinearLayout {
public interface OnSlideFinishListener {
void onStatueChanged(CurrentTargetIndex status);
}
public enum CurrentTargetIndex {
UPSTAIRS,
DOWNSTAIRS;
public static CurrentTargetIndex valueOf(int index) {
return 1 == index ? DOWNSTAIRS : UPSTAIRS;
}
}
private static final float DEFAULT_PERCENT = 0.3f;
private static final int DEFAULT_DURATION = 300;
private int mMaxFlingVelocity;
private int mMiniFlingVelocity;
private int mDefaultPanel = 0;
private int mDuration = DEFAULT_DURATION;
private float mTouchSlop;
private float mDownMotionY;
private float mDownMotionX;
private float mInitialInterceptY;
public void setPercent(float percent) {
mPercent = percent;
}
private float mPercent = DEFAULT_PERCENT;
/**
* flag for listview or scrollview ,if child overscrolled ,do not judge view region 滚过头了,还是可以滚动
*/
private boolean mChildHasScrolled;
private View mUpstairsView;
private View mDownstairsView;
private View mCurrentTargetView;
private Scroller mScroller;
private VelocityTracker mVelocityTracker;
private OnSlideFinishListener mOnSlideDetailsListener;
private CurrentTargetIndex mCurrentViewIndex = CurrentTargetIndex.UPSTAIRS;
public DragScrollLayout(Context context) {
this(context, null);
}
public DragScrollLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DragScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DragScrollLayout, defStyleAttr, 0);
mPercent = a.getFloat(R.styleable.DragScrollLayout_percent, DEFAULT_PERCENT);
mDuration = a.getInt(R.styleable.DragScrollLayout_duration, DEFAULT_DURATION);
mDefaultPanel = a.getInt(R.styleable.DragScrollLayout_default_panel, 0);
a.recycle();
mScroller = new Scroller(getContext(), new DecelerateInterpolator());
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
mMaxFlingVelocity = ViewConfiguration.get(getContext()).getScaledMaximumFlingVelocity();
mMiniFlingVelocity = ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity();
setOrientation(VERTICAL);
}
public void setOnSlideDetailsListener(OnSlideFinishListener listener) {
this.mOnSlideDetailsListener = listener;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
final int childCount = getChildCount();
if (1 >= childCount) {
throw new RuntimeException("SlideDetailsLayout only accept childs more than 1!!");
}
mUpstairsView = getChildAt(0);
mDownstairsView = getChildAt(1);
}
/**
* requestDisallowInterceptTouchEvent guarantee DragScrollLayout intercept event as wish
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!mScroller.isFinished()) {
resetDownPosition(ev);
return true;
}
Log.v("lishang", "" + getScrollY());
requestDisallowInterceptTouchEvent(false);
return super.dispatchTouchEvent(ev);
}
/**
* intercept rules:
* 1. The vertical displacement is larger than the horizontal displacement;
* 2. Panel stauts is UPSTAIRS: slide up
* 3. Panel status is DOWNSTAIRS:slide down
* 4. child can requestDisallowInterceptTouchEvent();
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
resetDownPosition(ev);
break;
case MotionEvent.ACTION_MOVE:
adjustValidDownPoint(ev);
return checkCanInterceptTouchEvent(ev);
default:
break;
}
return false;
}
private void resetDownPosition(MotionEvent ev) {
mDownMotionX = ev.getX();
mDownMotionY = ev.getY();
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.clear();
mChildHasScrolled = false;
mInitialInterceptY = (int) ev.getY();
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
flingToFinishScroll();
recycleVelocityTracker();
break;
case MotionEvent.ACTION_MOVE:
scroll(ev);
break;
default:
break;
}
return true;
}
private boolean checkCanInterceptTouchEvent(MotionEvent ev) {
final float xDiff = ev.getX() - mDownMotionX;
final float yDiff = ev.getY() - mDownMotionY;
if (!canChildScrollVertically((int) yDiff, ev)) {
mInitialInterceptY = (int) ev.getY();
if (Math.abs(yDiff) > mTouchSlop && Math.abs(yDiff) >= Math.abs(xDiff)
&& !(mCurrentViewIndex == CurrentTargetIndex.UPSTAIRS && yDiff > 0
|| mCurrentViewIndex == CurrentTargetIndex.DOWNSTAIRS && yDiff < 0)) {
return true;
}
}
return false;
}
private void adjustValidDownPoint(MotionEvent event) {
if (mCurrentViewIndex == CurrentTargetIndex.UPSTAIRS && event.getY() > mDownMotionY
|| mCurrentViewIndex == CurrentTargetIndex.DOWNSTAIRS && event.getY() < mDownMotionY) {
mDownMotionX = event.getX();
mDownMotionY = event.getY();
}
}
/**
* 拦截之后的拖动
*/
private void scroll(MotionEvent event) {
if (mCurrentViewIndex == CurrentTargetIndex.UPSTAIRS) {
if (getScrollY() <= 0 && event.getY() >= mInitialInterceptY) {
mInitialInterceptY = (int) event.getY();
}
int distance = mInitialInterceptY - event.getY() >= 0 ? (int) (mInitialInterceptY - event.getY()) : 0;
scrollTo(0, distance);
} else {
if (getScrollY() >= mUpstairsView.getMeasuredHeight() && event.getY() <= mInitialInterceptY) {
mInitialInterceptY = (int) event.getY();
}
int distance = event.getY() <= mInitialInterceptY ? mUpstairsView.getMeasuredHeight()
: (int) (mInitialInterceptY - event.getY() + mUpstairsView.getMeasuredHeight());
scrollTo(0, distance);
}
mVelocityTracker.addMovement(event);
}
/**
* 清理VelocityTracker
*/
private void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.clear();
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
/**
* if speed is enough even though offset is not enough go
*/
private void flingToFinishScroll() {
final int pHeight = mUpstairsView.getMeasuredHeight();
final int threshold = (int) (pHeight * mPercent);
float needFlingDistance = 0;
if (CurrentTargetIndex.UPSTAIRS == mCurrentViewIndex) {
if (getScrollY() <= 0) {
needFlingDistance = 0;
} else if (getScrollY() <= threshold) {
if (needFlingToToggleView()) {
needFlingDistance = pHeight - getScrollY();
mCurrentViewIndex = CurrentTargetIndex.DOWNSTAIRS;
} else {
needFlingDistance = -getScrollY();
}
} else {
needFlingDistance = pHeight - getScrollY();
mCurrentViewIndex = CurrentTargetIndex.DOWNSTAIRS;
}
} else if (CurrentTargetIndex.DOWNSTAIRS == mCurrentViewIndex) {
if (pHeight <= getScrollY()) {
needFlingDistance = 0;
} else if (pHeight - getScrollY() < threshold) {
if (needFlingToToggleView()) {
needFlingDistance = -getScrollY();
mCurrentViewIndex = CurrentTargetIndex.UPSTAIRS;
} else {
needFlingDistance = pHeight - getScrollY();
}
} else {
needFlingDistance = -getScrollY();
mCurrentViewIndex = CurrentTargetIndex.UPSTAIRS;
}
}
mScroller.startScroll(0, getScrollY(), 0, (int) needFlingDistance, mDuration);
if (mOnSlideDetailsListener != null) {
mOnSlideDetailsListener.onStatueChanged(mCurrentViewIndex);
}
postInvalidate();
}
private boolean needFlingToToggleView() {
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
if (mCurrentViewIndex == CurrentTargetIndex.UPSTAIRS) {
if (-mVelocityTracker.getYVelocity() > mMiniFlingVelocity) {
return true;
}
} else {
if (mVelocityTracker.getYVelocity() > mMiniFlingVelocity) {
return true;
}
}
return false;
}
private View getCurrentTargetView() {
return mCurrentViewIndex == CurrentTargetIndex.UPSTAIRS
? mUpstairsView : mDownstairsView;
}
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
scrollTo(0, mScroller.getCurrY());
postInvalidate();
}
}
/***
* 复用已经实现的View,省却了测量布局之类的麻烦
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
}
protected boolean canChildScrollVertically(int offSet, MotionEvent ev) {
mCurrentTargetView = getCurrentTargetView();
return canScrollVertically(mCurrentTargetView, -offSet, ev);
}
/***
* judge is event is in current view
* 判断MotionEvent是否处于View上面
*/
protected boolean isTransformedTouchPointInView(MotionEvent ev, View view) {
float x = ev.getRawX();
float y = ev.getRawY();
int[] rect = new int[2];
view.getLocationInWindow(rect);
float localX = x - rect[0];
float localY = y - rect[1];
return localX >= 0 && localX < (view.getRight() - view.getLeft())
&& localY >= 0 && localY < (view.getBottom() - view.getTop());
}
/***
* first can view self ScrollVertically
* seconde if View is ViewPager only judge current page
* third if view is viewgroup check it`s children
*/
private boolean canScrollVertically(View view, int offSet, MotionEvent ev) {
if (!mChildHasScrolled && !isTransformedTouchPointInView(ev, view)) {
return false;
}
if (ViewCompat.canScrollVertically(view, offSet)) {
mChildHasScrolled = true;
return true;
}
if (view instanceof ViewPager) {
return canViewPagerScrollVertically((ViewPager) view, offSet, ev);
}
if (view instanceof ViewGroup) {
ViewGroup vGroup = (ViewGroup) view;
for (int i = 0; i < vGroup.getChildCount(); i++) {
if (canScrollVertically(vGroup.getChildAt(i), offSet, ev)) {
mChildHasScrolled = true;
return true;
}
}
}
return false;
}
private boolean canViewPagerScrollVertically(ViewPager viewPager, int offset, MotionEvent ev) {
if(viewPager.getAdapter() instanceof DragFragmentPagerAdapter){
View showView = ((DragFragmentPagerAdapter) viewPager.getAdapter()).getPrimaryItem();
return showView != null && canScrollVertically(showView, offset, ev);
}else {
return false;
}
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
}
public void scrollToTop() {
if (mCurrentViewIndex == CurrentTargetIndex.DOWNSTAIRS) {
mScroller.startScroll(0, getScrollY(), 0, -getScrollY(), mDuration);
mCurrentViewIndex= CurrentTargetIndex.UPSTAIRS;
postInvalidate();
}
if (mOnSlideDetailsListener != null) {
mOnSlideDetailsListener.onStatueChanged(mCurrentViewIndex);
}
}
}
2.DragFragmentPagerAdapter(父控件中用到的碎片滑动适配器,用于处理tabView填充fragment)——-所有fragment导包都导v4包
public abstract class DragFragmentPagerAdapter extends FragmentPagerAdapter {
private View mCurrentView;
public DragFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
if (object instanceof View) {
mCurrentView = (View) object;
} else if (object instanceof Fragment) {
Fragment fragment = (Fragment) object;
mCurrentView = fragment.getView();
}
}
public View getPrimaryItem() {
return mCurrentView;
}
}
3.写在values文件夹styles中父控件所用到的样式
<resources>
...
<declare-styleable name="DragScrollDetailsLayout">
<attr name="percent" format="float"/>
<attr name="duration" format="integer"/>
<attr name="default_panel" format="enum">
<enum name="front" value="0"/>
<enum name="behind" value="1"/>
attr>
declare-styleable>
resources>
二,准备工作做好后我们步入正题。
在布局文件中使用我们自定义的布局
1.布局文件,我项目中这个布局文件的名字是fragment_home.xml,布局中隔控件的用途注释中写的很清楚
"1.0" encoding="utf-8"?>
.myceshi.DragScrollLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drag_content"
android:orientation="vertical"
android:background="@color/white"
android:layout_width="match_parent"
android:layout_height="match_parent">
"match_parent"
android:layout_height="wrap_content"
android:fillViewport="false">
"match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
//这里我放了一个banner图,这里可以放任何你想放到tab上面的东西,都将处于外层滑动之中,数量多少无所谓。
<com.youth.banner.Banner
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/banner"
android:layout_width="match_parent"
android:layout_height="150dp" />
//这里是分界线下面所有控件用于TabLayout,如不清楚可以查看TabLayout的用法。
.support.design.widget.TabLayout
android:id="@+id/tablayout"
android:layout_height="60dp"
android:layout_width="match_parent"
android:background="@color/borr_datils_bule"
app:tabIndicatorColor="@color/tab_golden"
app:tabIndicatorHeight="2dp"
app:tabSelectedTextColor="@color/tab_golden"
app:tabTextColor="@color/white"
>
.support.design.widget.TabLayout>
.support.v4.view.ViewPager
android:id="@+id/vp"
android:paddingBottom="50dp"//该行代码在activity中使用时不需要,只有在fragment中添加时才需要。
android:layout_height="match_parent"
android:layout_width="match_parent"
>
.support.v4.view.ViewPager>
.myceshi.DragScrollLayout>
我们可以将我们想放在tab上面(滑动外层的所有控件)都写在Scrollview中。
至于在父控件中添加的其他布局和Tablayout控件正常使用即可。
如果大家对效果图中的banner感兴趣可以查看
http://blog.csdn.net/qq_36621990/article/details/76020236
对Tablay不是很了解的可以看一下这篇文章
http://blog.csdn.net/qq_36621990/article/details/79129307