之前做过一个viewpager嵌套页面:可上下滑动的Layout中嵌套一个自定义的ViewPager,viewPager中嵌套FlowLayout。
整体效果如下图1所示。
上滑到顶部效果如图2:
代码清单文件如下:
1.ScrollableLayout.java
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.LinearLayout;
import android.widget.Scroller;
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.DetailFragment.java
计算ViewPager高度。
mCustomViewPager.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (mCustomViewPager.getHeight() > 0)
mCustomViewPager.getViewTreeObserver().removeGlobalOnLayoutListener(this);
FlowLayout flow = null;
for (int i = 0; i < mCustomViewPager.getChildCount(); i++) {
flow = (FlowLayout) mCustomViewPager.getChildAt(i);
if (null != flow && mFlowLayoutHeight < (flow.getChildHeights() + flow.getTotalLines() * 14)) {
mFlowLayoutHeight = flow.getChildHeights() + flow.getTotalLines() * 14;
ViewGroup.LayoutParams lp = mCustomViewPager.getLayoutParams();
lp.height = LocalDisplay.dp2px(mFlowLayoutHeight);
mCustomViewPager.setLayoutParams(lp);
}
}
}
});
标题栏滑动渐变
mScrollLayout.setOnScrollListener(new ScrollableLayout.OnScrollListener() {
@Override
public void onScroll(int currentY, int maxY) {
ViewHelper.setTranslationY(mRlhead, (float) (currentY * 0.5));
if (currentY <= 0) {
mTvBarTitle.setTextColor(Color.argb((int) 0, 66, 66, 66));//AGB由相关工具获得,或者美工提供或者美工提供
mLine.setBackgroundColor(Color.argb((int) 0, 217, 217, 217));
mTitleWholeArea.setBackgroundColor(Color.argb((int) 0, 251, 251, 251));
} else if (currentY > 0 && currentY <= mHeight) {
float scale = (float) currentY / mHeight;
float alpha = (255 * scale);
// 只是layout背景透明(仿知乎滑动效果)
mTvBarTitle.setTextColor(Color.argb((int) alpha, 66, 66, 66));
mLine.setBackgroundColor(Color.argb((int) alpha, 217, 217, 217));
mTitleWholeArea.setBackgroundColor(Color.argb((int) alpha, 251, 251, 251));
} else {
mTvBarTitle.setTextColor(Color.argb((int) 255, 66, 66, 66));
mLine.setBackgroundColor(Color.argb((int) 255, 217, 217, 217));
mTitleWholeArea.setBackgroundColor(Color.argb((int) 255, 251, 251, 251));
}
}
});
布局文件如下
<com.xw.merchant.widget.scrollable.ScrollableLayout
android:id="@+id/scrollableLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/ll_title_bar"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/rl_head"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/_white"
android:orientation="vertical">
<TextView
android:id="@+id/tv_customer_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/_gap_large"
android:text="黑手未入党"
android:textColor="@color/_color_gray5"
android:textSize="@dimen/_space_xxxlarge"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/_space_small"
android:gravity="center"
android:orientation="horizontal">
<View
android:layout_width="@dimen/xw_gap_team_item_xxxlarge"
android:layout_height="@dimen/_line_thin"
android:layout_gravity="center"
android:layout_marginRight="@dimen/_gap_small"
android:background="@color/_textcolorHint" />
<TextView
android:id="@+id/tv_customer_mobile"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_horizontal"
android:text="18617080737"
android:textColor="@color/_color_gray5"
android:textSize="@dimen/_text_size_medium" />
<View
android:layout_width="@dimen/_gap_team_item_xxxlarge"
android:layout_height="@dimen/_line_thin"
android:layout_gravity="center"
android:layout_marginLeft="@dimen/_gap_small"
android:background="@color/_textcolorHint" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/_space_small"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_my_customer_state"
android:layout_width="wrap_content"
android:layout_height="@dimen/_text_size_large"
android:background="@drawable/_sl_not_assign_customer"
android:gravity="center"
android:paddingLeft="@dimen/_space_small"
android:paddingRight="@dimen/_space_small"
android:text="@string/_consumption_un_assign"
android:textColor="@color/_white"
android:textSize="@dimen/_space_small_nine" />
<TextView
android:id="@+id/tv_staff_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/_space_tiny"
android:gravity="center"
android:text="@string/_customer_distribution_status2"
android:textColor="@color/_color_yellow"
android:textSize="@dimen/_gap_small"
android:visibility="gone" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="@dimen/_gap_team_item_xxxlarge"
android:layout_marginRight="@dimen/_gap_team_item_xxxlarge"
android:orientation="vertical">
<TextView
android:id="@+id/tv_hd_empty"
android:layout_width="1dp"
android:layout_height="@dimen/_gap_medium"
android:background="@color/_white"
android:gravity="center"
android:visibility="invisible" />
<com.xw.merchant.widget.CustomViewPager
android:id="@+id/customViewPager"
android:layout_width="wrap_content"
android:layout_height="1dp"
android:gravity="center" />
<com.xw.merchant.widget.indicator.CirclePageIndicator
android:id="@+id/indicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:visibility="gone"
app:fillColor="@color/_textcolor_assist"
app:pageColor="#32000000"
app:strokeWidth="0dp" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/_white"
android:gravity="center"
android:orientation="horizontal"
android:paddingBottom="@dimen/_xxxlarge"
android:paddingTop="@dimen/_xxxlarge">
<ImageView
android:id="@+id/iv_btn_phone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:src="@drawable/_phone" />
<ImageView
android:id="@+id/iv_btn_send_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="@dimen/_xxxlarge"
android:src="@drawable/_sms" />
<ImageView
android:id="@+id/iv_btn_upd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="@dimen/_xxxlarge"
android:src="@drawable/_upd" />
<ImageView
android:id="@+id/iv_btn_del"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="@dimen/_xxxlarge"
android:src="@drawable/_del" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/tab_sliding_back"
android:gravity="center">
<com.xw.merchant.widget.scrollable.CustomPagerSlidingTabStrip
android:id="@+id/pagerStrip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_gravity="center_horizontal"
android:background="@color/transparent"></com.xw.merchant.widget.scrollable.CustomPagerSlidingTabStrip>
</LinearLayout>
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.xw.merchant.widget.scrollable.ScrollableLayout>