ScrollableLayout +viewpager实现标题悬浮

参考来源

1.自定义ScrollableLayout类:

public class ScrollableLayout extends LinearLayout {

private final String tag = "cp:scrollableLayout";
private float mDownX;
private float mDownY;
private float mLastY;

private int minY = 0;
private int maxY = 0;
private int mHeadHeight;
private int mExpandHeight;
private int mTouchSlop;
private int mMinimumVelocity;
private int mMaximumVelocity;
// 方向
private DIRECTION mDirection;
private int mCurY;
private int mLastScrollerY;
private boolean needCheckUpdown;
private boolean updown;
private boolean mDisallowIntercept;
private boolean isClickHead;
private boolean isClickHeadExpand;

private View mHeadView;
private ViewPager childViewPager;

private Scroller mScroller;
private VelocityTracker mVelocityTracker;

/**
 * 滑动方向 *
 */
enum DIRECTION {
    UP,// 向上划
    DOWN// 向下划
}

public interface OnScrollListener {

    void onScroll(int currentY, int maxY);

}

private OnScrollListener onScrollListener;

public void setOnScrollListener(OnScrollListener onScrollListener) {
    this.onScrollListener = onScrollListener;
}

private ScrollableHelper mHelper;

public ScrollableHelper getHelper() {
    return mHelper;
}

public ScrollableLayout(Context context) {
    super(context);
    init(context);
}

public ScrollableLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context);
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public ScrollableLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init(context);
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ScrollableLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    init(context);
}

private void init(Context context) {
    mHelper = new ScrollableHelper();
    mScroller = new Scroller(context);
    final ViewConfiguration configuration = ViewConfiguration.get(context);
    mTouchSlop = configuration.getScaledTouchSlop();
    mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
    mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
}

public boolean isSticked() {
    return mCurY == maxY;
}

/**
 * 扩大头部点击滑动范围
 *
 * @param expandHeight
 */
public void setClickHeadExpand(int expandHeight) {
    mExpandHeight = expandHeight;
}

public int getMaxY() {
    return maxY;
}

public boolean isHeadTop() {
    return mCurY == minY;
}

public boolean canPtr() {
    return updown && mCurY == minY && mHelper.isTop();
}

public void requestScrollableLayoutDisallowInterceptTouchEvent(boolean disallowIntercept) {
    super.requestDisallowInterceptTouchEvent(disallowIntercept);
    mDisallowIntercept = disallowIntercept;
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    float currentX = ev.getX();
    float currentY = ev.getY();
    float deltaY;
    int shiftX = (int) Math.abs(currentX - mDownX);
    int shiftY = (int) Math.abs(currentY - mDownY);
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mDisallowIntercept = false;
            needCheckUpdown = true;
            updown = true;
            mDownX = currentX;
            mDownY = currentY;
            mLastY = currentY;
            checkIsClickHead((int) currentY, mHeadHeight, getScrollY());
            checkIsClickHeadExpand((int) currentY, mHeadHeight, getScrollY());
            initOrResetVelocityTracker();
            mVelocityTracker.addMovement(ev);
            mScroller.forceFinished(true);
            break;
        case MotionEvent.ACTION_MOVE:
            if (mDisallowIntercept) {
                break;
            }
            initVelocityTrackerIfNotExists();
            mVelocityTracker.addMovement(ev);
            deltaY = mLastY - currentY;
            if (needCheckUpdown) {
                if (shiftX > mTouchSlop && shiftX > shiftY) {
                    needCheckUpdown = false;
                    updown = false;
                } else if (shiftY > mTouchSlop && shiftY > shiftX) {
                    needCheckUpdown = false;
                    updown = true;
                }
            }

            if (updown && shiftY > mTouchSlop && shiftY > shiftX &&
                    (!isSticked() || mHelper.isTop() || isClickHeadExpand)) {

                if (childViewPager != null) {
                    childViewPager.requestDisallowInterceptTouchEvent(true);
                }
                scrollBy(0, (int) (deltaY + 0.5));
            }
            mLastY = currentY;
            break;
        case MotionEvent.ACTION_UP:
            if (updown && shiftY > shiftX && shiftY > mTouchSlop) {
                mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                float yVelocity = -mVelocityTracker.getYVelocity();
                boolean dislowChild = false;
                if (Math.abs(yVelocity) > mMinimumVelocity) {
                    mDirection = yVelocity > 0 ? DIRECTION.UP : DIRECTION.DOWN;
                    if ((mDirection == DIRECTION.UP && isSticked()) || (!isSticked() && getScrollY() == 0 && mDirection == DIRECTION.DOWN)) {
                        dislowChild = true;
                    } else {
                        mScroller.fling(0, getScrollY(), 0, (int) yVelocity, 0, 0, -Integer.MAX_VALUE, Integer.MAX_VALUE);
                        mScroller.computeScrollOffset();
                        mLastScrollerY = getScrollY();
                        invalidate();
                    }
                }
                if (!dislowChild && (isClickHead || !isSticked())) {
                    int action = ev.getAction();
                    ev.setAction(MotionEvent.ACTION_CANCEL);
                    boolean dispathResult = super.dispatchTouchEvent(ev);
                    ev.setAction(action);
                    return dispathResult;
                }
            }
            break;
        default:
            break;
    }
    super.dispatchTouchEvent(ev);
    return true;
}

@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private int getScrollerVelocity(int distance, int duration) {
    if (mScroller == null) {
        return 0;
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
        return (int) mScroller.getCurrVelocity();
    } else {
        return distance / duration;
    }
}

@Override
public void computeScroll() {
    if (mScroller.computeScrollOffset()) {
        final int currY = mScroller.getCurrY();
        if (mDirection == DIRECTION.UP) {
            // 手势向上划
            if (isSticked()) {
                int distance = mScroller.getFinalY() - currY;
                int duration = calcDuration(mScroller.getDuration(), mScroller.timePassed());
                mHelper.smoothScrollBy(getScrollerVelocity(distance, duration), distance, duration);
                mScroller.forceFinished(true);
                return;
            } else {
                scrollTo(0, currY);
            }
        } else {
            // 手势向下划
            if (mHelper.isTop() || isClickHeadExpand) {
                int deltaY = (currY - mLastScrollerY);
                int toY = getScrollY() + deltaY;
                scrollTo(0, toY);
                if (mCurY <= minY) {
                    mScroller.forceFinished(true);
                    return;
                }
            }
            invalidate();
        }
        mLastScrollerY = currY;
    }
}

@Override
public void scrollBy(int x, int y) {
    int scrollY = getScrollY();
    int toY = scrollY + y;
    if (toY >= maxY) {
        toY = maxY;
    } else if (toY <= minY) {
        toY = minY;
    }
    y = toY - scrollY;
    super.scrollBy(x, y);
}

@Override
public void scrollTo(int x, int y) {
    if (y >= maxY) {
        y = maxY;
    } else if (y <= minY) {
        y = minY;
    }
    mCurY = y;
    if (onScrollListener != null) {
        onScrollListener.onScroll(y, maxY);
    }
    super.scrollTo(x, y);
}

private void initOrResetVelocityTracker() {
    if (mVelocityTracker == null) {
        mVelocityTracker = VelocityTracker.obtain();
    } else {
        mVelocityTracker.clear();
    }
}

private void initVelocityTrackerIfNotExists() {
    if (mVelocityTracker == null) {
        mVelocityTracker = VelocityTracker.obtain();
    }
}

private void recycleVelocityTracker() {
    if (mVelocityTracker != null) {
        mVelocityTracker.recycle();
        mVelocityTracker = null;
    }
}

private void checkIsClickHead(int downY, int headHeight, int scrollY) {
    isClickHead = downY + scrollY <= headHeight;
}

private void checkIsClickHeadExpand(int downY, int headHeight, int scrollY) {
    if (mExpandHeight <= 0) {
        isClickHeadExpand = false;
    }
    isClickHeadExpand = downY + scrollY <= headHeight + mExpandHeight;
}

private int calcDuration(int duration, int timepass) {
    return duration - timepass;
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    mHeadView = getChildAt(0);
    measureChildWithMargins(mHeadView, widthMeasureSpec, 0, MeasureSpec.UNSPECIFIED, 0);
    maxY = mHeadView.getMeasuredHeight();
    mHeadHeight = mHeadView.getMeasuredHeight();
    super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec) + maxY, MeasureSpec.EXACTLY));
}

@Override
protected void onFinishInflate() {
    if (mHeadView != null && !mHeadView.isClickable()) {
        mHeadView.setClickable(true);
    }
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        View childAt = getChildAt(i);
        if (childAt != null && childAt instanceof ViewPager) {
            childViewPager = (ViewPager) childAt;
        }
    }
    super.onFinishInflate();
}

}

2.重写ScrollableHelper类:

public class ScrollableHelper {

private ScBaseFragment mCurrentScrollableCainer;

/**
 * a viewgroup whitch contains ScrollView/ListView/RecycelerView..
 */
public interface ScrollableContainer{
    /**
     * @return ScrollView/ListView/RecycelerView..'s instance
     */
    View getScrollableView();
}

public void setCurrentScrollableContainer(ScBaseFragment scrollableContainer) {
    this.mCurrentScrollableCainer = scrollableContainer;
}

private View getScrollableView() {
    if (mCurrentScrollableCainer == null) {
        return null;
    }
    return mCurrentScrollableCainer.getScrollableView();
}

/**
 *
 * 判断是否滑动到顶部方法,ScrollAbleLayout根据此方法来做一些逻辑判断
 * 目前只实现了AdapterView,ScrollView,RecyclerView
 * 需要支持其他view可以自行补充实现
 * @return
 */
public boolean isTop() {
    View scrollableView = getScrollableView();
    if (scrollableView == null) {
//            throw new NullPointerException("You should call ScrollableHelper.setCurrentScrollableContainer() to set ScrollableContainer.");
        return true;
    }
    if (scrollableView instanceof AdapterView) {  //ListView
        return isAdapterViewTop((AdapterView) scrollableView);
    }
    if (scrollableView instanceof ScrollView) {
        return isScrollViewTop((ScrollView) scrollableView);
    }
    if (scrollableView instanceof RecyclerView) {
        return isRecyclerViewTop((RecyclerView) scrollableView);
    }
    if (scrollableView instanceof WebView) {
        return isWebViewTop((WebView) scrollableView);
    }
    throw new IllegalStateException("scrollableView must be a instance of AdapterView|ScrollView|RecyclerView");
}

private static boolean isRecyclerViewTop(RecyclerView recyclerView) {
    if (recyclerView != null) {
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof LinearLayoutManager) {
            int firstVisibleItemPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
            View childAt = recyclerView.getChildAt(0);
            if (childAt == null || (firstVisibleItemPosition == 0 &&
                    layoutManager.getDecoratedTop(childAt) == 0)) {
                return true;
            }
        }
    }
    return false;
}

private static boolean isAdapterViewTop(AdapterView adapterView){
    if(adapterView != null){
        int firstVisiblePosition = adapterView.getFirstVisiblePosition();
        View childAt = adapterView.getChildAt(0);
        if(childAt == null || (firstVisiblePosition == 0 && childAt.getTop() == 0)){
            return true;
        }
    }
    return false;
}

private static boolean isScrollViewTop(ScrollView scrollView){
    if(scrollView != null) {
        int scrollViewY = scrollView.getScrollY();
        return scrollViewY <= 0;
    }
    return false;
}

private static boolean isWebViewTop(WebView scrollView){
    if(scrollView != null) {
        int scrollViewY = scrollView.getScrollY();
        return scrollViewY <= 0;
    }
    return false;
}

@SuppressLint("NewApi")
public void smoothScrollBy(int velocityY, int distance, int duration) {
    View scrollableView = getScrollableView();
    if (scrollableView instanceof AbsListView) {
        AbsListView absListView = (AbsListView) scrollableView;
        if (Build.VERSION.SDK_INT >= 21) {
            absListView.fling(velocityY);
        } else {
            absListView.smoothScrollBy(distance, duration);
        }
    } else if (scrollableView instanceof ScrollView) {
        ((ScrollView) scrollableView).fling(velocityY);
    } else if (scrollableView instanceof RecyclerView) {
        ((RecyclerView) scrollableView).fling(0, velocityY);
    } else if (scrollableView instanceof WebView) {
        ((WebView) scrollableView).flingScroll(0, velocityY);
    }
}

}

3.布局 ScrollableLayout包裹

<?xml version="1.0" encoding="utf-8"?>
	<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
>
<com.example.scroll_viewpager.ScrollableLayout
    android:id="@+id/sl_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:src="@mipmap/home_srarch_bg"
        />


    <!--标头-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:orientation="horizontal"
        >
        <RelativeLayout
            android:id="@+id/ly_page1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:clickable="true"
            android:background="#fff"
         >

            <TextView
                android:id="@+id/tv_page1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:text="商品详情"
                android:textColor="@color/colorAccent"
                android:textSize="13sp"
                />
            <View
                android:id="@+id/iv_page1_expand"
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:layout_alignParentBottom="true"
                android:background="@color/colorAccent"
                />
        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/ly_page2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:clickable="true"
            android:background="#fff"
         >
            <TextView
                android:id="@+id/tv_page2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:text="商品配置"
                android:textColor="#435356" />
            <View
                android:id="@+id/iv_page2_expand"
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:layout_alignParentBottom="true"
                android:background="@color/colorAccent"
                android:visibility="invisible"
                />
        </RelativeLayout>
    </LinearLayout>

    <!--viewpager-->
    <androidx.viewpager.widget.ViewPager
        android:id="@+id/vp_scroll"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#f1f1f1" />
</com.example.scroll_viewpager.ScrollableLayout>
</LinearLayout>

4.使用:MainActivity

package com.example.scroll_viewpager;

import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import android.os.Bundle;
import android.view.View;
import android.widget.RelativeLayout;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
/**
 * ScrollableLayout  +viewpager
 * 固定标签栏在顶部
 * @param savedInstanceState
 */

ScrollableLayout slRoot;
ViewPager vpScroll;
RelativeLayout ly_page1,ly_page2;
private final List<ScBaseFragment> fragmentList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initView();
    initClick();

}
private void initView() {
    slRoot =findViewById(R.id.sl_root);
    vpScroll =findViewById(R.id.vp_scroll);
    ly_page1 =findViewById(R.id.ly_page1);
    ly_page2 =findViewById(R.id.ly_page2);
    //viewpager 添加fragment
    FrameLayout01 layout01 = new FrameLayout01();
    FrameLayout02 layout02 = new FrameLayout02();
    fragmentList.add(layout01);
    fragmentList.add(layout02);
    CommonFragementPagerAdapter commonFragementPagerAdapter = new CommonFragementPagerAdapter(getSupportFragmentManager());
    vpScroll.setAdapter(commonFragementPagerAdapter);
    //重要1:
    slRoot.getHelper().setCurrentScrollableContainer(fragmentList.get(0));

    vpScroll.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

        }
        @Override
        public void onPageSelected(int position) {
            //重要2:
            slRoot.getHelper().setCurrentScrollableContainer(fragmentList.get(position));
        }

        @Override
        public void onPageScrollStateChanged(int state) {

        }
    });


}

public void initClick(){
    ly_page1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            vpScroll.setCurrentItem(0);
        }
    });

    ly_page2.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            vpScroll.setCurrentItem(1);
        }
    });
}

public class CommonFragementPagerAdapter extends FragmentPagerAdapter {

    public CommonFragementPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return getCount() > position ? fragmentList.get(position) : null;
    }

    @Override
    public int getCount() {
        return fragmentList == null ? 0 : fragmentList.size();
    }
}
}

5.Fragment:

public class FrameLayout02 extends ScBaseFragment {
ListView list_view02;

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragmment02, null);
    list_view02 =view.findViewById(R.id.list_view02);

    return view;
}
	//重要 
@Override
public View getScrollableView() {
    return list_view02;
}
}

6.ScBaseFragment :

public abstract class ScBaseFragment extends Fragment implements ScrollableHelper.ScrollableContainer {
	}

注意:

1.初始化时ScrollableLayout获取第一个fragment的滚动容器:
	slRoot.getHelper().setCurrentScrollableContainer(fragmentList.get(0));
2.viewpager的addOnPageChangeListener方法中的onPageSelected重新对ScrollableLayout设置fragment:
	slRoot.getHelper().setCurrentScrollableContainer(fragmentList.get(position));	
3.导入依赖:
	 //固定标签栏在顶部
	 api 'com.github.w446108264:ScrollableLayout:1.0.3'	

小demo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值