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'