很多APP首页都是采用头部+列表的形式显示,但是首页的空间有限,这时就需要把头部布局折叠起来了,参考效果如下:
Google在Design库22+上面增加了CoordinatorLayout+Behavior可以实现这种效y果,但是实现起来相对比较复杂,下面我将用另一种方式实现。
工程已经上传aizuzi/FoldLayout,欢迎大家给start
效果图
实现步骤
第一步
我们自定义一个控件作为头部显示,实现复杂的折叠效果也是在该类完成,在这里我先放一个ImageView作为示例,具体代码如下
public class HeadView extends LinearLayout {
private ImageView imageview;
private float imagevViewY;
public HeadView(Context context) {
super(context);
init();
}
public HeadView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public HeadView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* 初始化头部布局
*/
private void init() {
imageview = new ImageView(getContext());
imageview.setImageResource(R.mipmap.ic_launcher);
addView(imageview,new LayoutParams(UnitUtil.dp2px(getContext(),80),UnitUtil.dp2px(getContext(),80)));
setGravity(Gravity.CENTER);
setBackgroundColor(getResources().getColor(R.color.colorAccent));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(imagevViewY ==0)
imagevViewY = imageview.getY();
}
/**
* 拖动时改变控件的位置
* @param percentage
*/
public void offsetPixel(float percentage) {
int viewHeight = getHeight();
imageview.setY(imagevViewY+(int) ((viewHeight-imageview.getHeight())/2*percentage));
}
}
第二步
下面的列表用一个自定义的RecyclerView显示,如果是使用ListView或者GridView可以重写dispatchTouchEvent方法来分发触摸事件
public class CustomRecyclerView extends RecyclerView {
private static final int TOUCH_IDLE = 0;
private static final int TOUCH_DRAG_LAYOUT = 1;
private int scrollMode;
private float downY;
private int minHeight;
boolean isAtTop;
public CustomRecyclerView(Context arg0) {
super(arg0);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (getTop() > minHeight) {
getParent().requestDisallowInterceptTouchEvent(false);
return false;
}
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
downY = ev.getRawY();
isAtTop = isReadyForPullStart();
scrollMode = TOUCH_IDLE;
getParent().requestDisallowInterceptTouchEvent(true);
} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
if (scrollMode == TOUCH_IDLE) {
float yDistance = Math.abs(downY - ev.getRawY());
if (yDistance > 0) {
scrollMode = TOUCH_DRAG_LAYOUT;
if (downY < ev.getRawY() && isAtTop) {
getParent().requestDisallowInterceptTouchEvent(false);
return false;
}
}
}
}
return super.dispatchTouchEvent(ev);
}
//传入头部布局的最小高度,用于判断分发触摸事件
public void setMinHeight(int minHeight) {
this.minHeight = minHeight;
}
//判断RecyclerView是否滚动到顶部
protected boolean isReadyForPullStart() {
if (getChildCount() <= 0)
return false;
View view = getChildAt(0);
int firstVisiblePosition = getChildAdapterPosition(view);
if (firstVisiblePosition == 0) {
return view.getTop() >= getPaddingTop();
} else {
return false;
}
}
}
第三步
实现折叠效果的关键在这里,采用自定义ViewGroup来管理上面两个空间的位置,在这里使用了support-v4包下的ViewDragHelper工具类来协调两个空间的位置。
核心代码在ViewDragHelper.Callback回调里,ViewDragHelper已经帮我们实现了拖拽控件,我们在回调里判断到了最小的高度则停止拖拽,并且通知子View实现自身的逻辑即可
private class MDragCallback extends ViewDragHelper.Callback {
@Override
public void onViewPositionChanged(View changedView, int left, int top,
int dx, int dy) {
//控件正在拖拽中回调
}
@Override
public boolean tryCaptureView(View child, int pointerId) {
//是否需要捕获child的拖动,为false则不拖动
return true;
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
//手指移开屏幕,这里需要做回弹的动画
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
//返回拖动后控件的坐标,child为拖动中的子控件
return top;
}
}
最后
第一次写教程还有很多地方写得不是很明白,还望大家体谅,如果大家有问题可以在后面留言或者发简信给我,我会尽快回复。
最后再重复一下,工程已经全部上传aizuzi/FoldLayout,欢迎大家去给start