google推出的coordinatorlayout对于能看不能用的项目来说是不是很是纠结,今天就来打造一个。
先上效果图哈
1.全部隐藏头部的
2.有固定一部分不隐藏的
是不是可以满足大部分的需求了,而且刷新控件可以随便换哦,换成啥都可以,这里为了测试就使用了v4包里面的SwipeRefreshLayout。
看完效果就来分析下该怎么做(下面的分析要有view的事件分发基础)
1.使用事件分发的拦截操作:这个方法有一个弊端,就是当onInterceptTouchEvent 拦截后ziview就不能获得事件了,也就是要先松下手才能继续滚动列表,这体验有点生硬,pass
2.事件都交给列表处理(比如都交给listview):这个有一个问题就是不滑动列表,滑动头部就不能滑动了。pass
3.有没有什么方法会在子view执行时父view也知道呢?哈哈。。是不是想到了dispatchTouchEvent()。
就是在父view的dispatchTouchEvent处理父view的滚动
先上下布局文件哈
<com.example.testfellow.RefreshLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_ll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.testfellow.MainActivity" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:layout_width="match_parent"
android:textSize="30sp"
android:layout_height="wrap_content"
android:text="hello row 1" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="30sp"
android:text="hello row 2" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="30sp"
android:text="hello row 3" />
<!--这里可以设置成固定在头部 -->
<LinearLayout
android:id="@+id/fixed_ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/darker_gray"
android:orientation="vertical" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="30sp"
android:text="hello row 22222" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="30sp"
android:text="hello row 222223" />
</LinearLayout>
</LinearLayout>
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/rfl"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
</android.support.v4.widget.SwipeRefreshLayout>
</com.example.testfellow.RefreshLinearLayout>
布局分两部分:头部和列表部分。
好现在来看下主角
测量方法
/**
* 拉长容器
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth = getDefaultSize(0, widthMeasureSpec);
// 一个头一个容器
measureChild(headerView, widthMeasureSpec, heightMeasureSpec);
measureChild(contentView, widthMeasureSpec, heightMeasureSpec);
headerViewHeight = headerView.getMeasuredHeight();
int measuredHeight = getDefaultSize(0, heightMeasureSpec)
+ headerViewHeight;
setMeasuredDimension(measuredWidth, measuredHeight);
if (fixedView != null) {
headerViewHeight -= fixedView.getMeasuredHeight();
}
}
测量方法是不是要把自己拉长来啊,让它能容纳两个view的高度
这样就可以滚动了 ,如果在滚动时在改变布局会很卡顿。所以先把他们的高度算好。
/**
* 控制父view 的滚动
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
handler.removeCallbacksAndMessages(null);
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
downY = lastY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
moveY = ev.getY();
destY = moveY - lastY;
// 条件判断
judgeCondination();
if (!isCanRefresh && !isCanMore) {
if (!isTouchSlopOk) {
final float touchDestY = downY - moveY;
if (Math.abs(touchDestY) > mTouchSlop) {
isTouchSlopOk = true;
}
} else {
// 处理跟随滚动
handleMove();
}
}
lastY = moveY;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
resetState();
// 为了使头部在滚动view到头部时一定可见
handler.sendMessageDelayed(handler.obtainMessage(), 1000);
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
return super.onInterceptTouchEvent(ev);
}
@Override
protected void onDetachedFromWindow() {
// TODO Auto-generated method stub
handler.removeCallbacksAndMessages(null);
super.onDetachedFromWindow();
}
/**
* 自动还原处理 不出现显示部分的情况
*/
private void autoScrollerHandler() {
// TODO Auto-generated method stub
final int scrollY = getScrollY();
if (mTarget instanceof AbsListView) {
final AbsListView absListView = (AbsListView) mTarget;
int firstVisiblePosition = absListView.getFirstVisiblePosition();
if (firstVisiblePosition == 0) {
View childView = getChildAt(0);
if (childView.getTop() < headerViewHeight) {// 显示
if (scrollY != 0)
mScroller.startScroll(0, scrollY, 0, -scrollY);
} else {// 自动判断
// conditionScroll(scrollY);
}
} else {
// conditionScroll(scrollY);
}
} else if (mTarget instanceof ScrollView) {// scrollview .....
final ScrollView scrollView = (ScrollView) mTarget;
if (scrollView.getScrollY() < headerViewHeight) {
if (scrollY != 0)
mScroller.startScroll(0, scrollY, 0, -scrollY);
} else {
// conditionScroll(scrollY);
}
}
ViewCompat.postInvalidateOnAnimation(this);
}
/**
* 暂时不用了
*
* @param scrollY
*/
private void conditionScroll(final int scrollY) {
if (scrollY < headerViewHeight / 2) {// show
if (scrollY != 0)
mScroller.startScroll(0, scrollY, 0, -scrollY);
} else {// hide
if (headerViewHeight != scrollY)
mScroller
.startScroll(0, scrollY, 0, headerViewHeight - scrollY);
}
}
@Override
public void computeScroll() {
// TODO Auto-generated method stub
super.computeScroll();
if (mScroller.computeScrollOffset()) {
int currY = mScroller.getCurrY();
scrollTo(0, currY);
ViewCompat.postInvalidateOnAnimation(this);
}
}
接下来的代码就更平时自定义滑动没多大区别了,只不过平时在onTouchEvent里面写代码,现在是在dispatchTouchEvent里面写。
比较简单就不分析了
再看下使用代码
private RefreshLinearLayout refresh_ll;
private SwipeRefreshLayout rfl;
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
refresh_ll = (RefreshLinearLayout) findViewById(R.id.root_ll);
rfl = (SwipeRefreshLayout) findViewById(R.id.rfl);
rfl.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh() {
// TODO Auto-generated method stub
rfl.postDelayed(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
rfl.setRefreshing(false);
}
}, 1500);
}
});
listView = (ListView) findViewById(R.id.lv);
ArrayList<String> datas = new ArrayList<>();
for (int i = 0; i < 50; i++) {
datas.add("item=" + i);
}
listView.setAdapter(new ArrayAdapter<>(this,
android.R.layout.simple_list_item_1, datas));
refresh_ll.setTargetView(listView);
View fixll = findViewById(R.id.fixed_ll);
//设置固定头部
// refresh_ll.setFixedView(fixll);
}
如果固定头部设置了就可以固定 不设置整个头部都会随着隐藏。
好了 代码传送 ..不知道出什么鬼了 cadn上传资源不显示
把全部代码贴下把
public class RefreshLinearLayout extends LinearLayout {
private float lastY;
private float downY;
private float moveY;
private float destY;
// 头部滚动的view
private View headerView;
// 能滚动的view
private View contentView;
// 头部高度
private int headerViewHeight;
// listview scrollView...
private View mTarget;
// 是否第一次
private boolean isFrist = true;
// 能刷新不
private boolean isCanRefresh;
// 能加载更多不
private boolean isCanMore;
// 是否满足最小滚动距离
private boolean isTouchSlopOk;
// 滚动辅助类
private Scroller mScroller;
private int mTouchSlop;
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
autoScrollerHandler();
};
};
private View fixedView;
public RefreshLinearLayout(Context context) {
super(context);
// TODO Auto-generated constructor stub
initView(context);
}
public RefreshLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
initView(context);
}
public RefreshLinearLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
initView(context);
}
private void initView(Context context) {
// TODO Auto-generated method stub
mScroller = new Scroller(context);
ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
mTouchSlop = ViewConfigurationCompat
.getScaledPagingTouchSlop(viewConfiguration);
}
/**
* 控制父view 的滚动
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
handler.removeCallbacksAndMessages(null);
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
downY = lastY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
moveY = ev.getY();
destY = moveY - lastY;
// 条件判断
judgeCondination();
if (!isCanRefresh && !isCanMore) {
if (!isTouchSlopOk) {
final float touchDestY = downY - moveY;
if (Math.abs(touchDestY) > mTouchSlop) {
isTouchSlopOk = true;
}
} else {
// 处理跟随滚动
handleMove();
}
}
lastY = moveY;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
resetState();
// 为了使头部在滚动view到头部时一定可见
handler.sendMessageDelayed(handler.obtainMessage(), 1000);
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
return super.onInterceptTouchEvent(ev);
}
@Override
protected void onDetachedFromWindow() {
// TODO Auto-generated method stub
handler.removeCallbacksAndMessages(null);
super.onDetachedFromWindow();
}
/**
* 自动还原处理 不出现显示部分的情况
*/
private void autoScrollerHandler() {
// TODO Auto-generated method stub
final int scrollY = getScrollY();
if (mTarget instanceof AbsListView) {
final AbsListView absListView = (AbsListView) mTarget;
int firstVisiblePosition = absListView.getFirstVisiblePosition();
if (firstVisiblePosition == 0) {
View childView = getChildAt(0);
if (childView.getTop() < headerViewHeight) {// 显示
if (scrollY != 0)
mScroller.startScroll(0, scrollY, 0, -scrollY);
} else {// 自动判断
// conditionScroll(scrollY);
}
} else {
// conditionScroll(scrollY);
}
} else if (mTarget instanceof ScrollView) {// scrollview .....
final ScrollView scrollView = (ScrollView) mTarget;
if (scrollView.getScrollY() < headerViewHeight) {
if (scrollY != 0)
mScroller.startScroll(0, scrollY, 0, -scrollY);
} else {
// conditionScroll(scrollY);
}
}
ViewCompat.postInvalidateOnAnimation(this);
}
/**
* 暂时不用了
*
* @param scrollY
*/
private void conditionScroll(final int scrollY) {
if (scrollY < headerViewHeight / 2) {// show
if (scrollY != 0)
mScroller.startScroll(0, scrollY, 0, -scrollY);
} else {// hide
if (headerViewHeight != scrollY)
mScroller
.startScroll(0, scrollY, 0, headerViewHeight - scrollY);
}
}
@Override
public void computeScroll() {
// TODO Auto-generated method stub
super.computeScroll();
if (mScroller.computeScrollOffset()) {
int currY = mScroller.getCurrY();
scrollTo(0, currY);
ViewCompat.postInvalidateOnAnimation(this);
}
}
/**
* 还原状态
*/
private void resetState() {
isFrist = true;
isCanRefresh = true;
isCanMore = true;
isTouchSlopOk = false;
}
/**
* 处理跟随滚动
*/
private void handleMove() {
final int scrollY = getScrollY();
int sy = (int) (-destY * 1.15f);
if (destY < 0) {// 向上滑
final int futureY = scrollY + sy;
if (futureY > headerViewHeight) {
sy = headerViewHeight - scrollY;
}
if (scrollY < headerViewHeight) {
scrollBy(0, sy);
}
} else {// 向下滑
final int futureY = scrollY + sy;
if (futureY < 0) {
sy = -scrollY;
}
if (scrollY > 0) {
scrollBy(0, sy);
}
}
}
/**
* 滚动前的必要条件判断
*/
private void judgeCondination() {
if (isFrist && destY != 0) {
isCanRefresh = !canChildScrollUp();
// 向上还是能滚动
if (destY < 0) {
isCanRefresh = false;
}
isCanMore = !canChildScrollDown();
// 向下还是能滚动
if (destY > 0) {
isCanMore = false;
}
isFrist = false;
}
}
/**
* 还能否向上滚动
*
* @return
*/
private boolean canChildScrollUp() {
if (mTarget == null) {
return true;
}
if (android.os.Build.VERSION.SDK_INT < 14) {
if (mTarget instanceof AbsListView) {
final AbsListView absListView = (AbsListView) mTarget;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView
.getChildAt(0).getTop() < absListView
.getPaddingTop());
} else {
return mTarget.getScrollY() > 0;
}
} else {
return ViewCompat.canScrollVertically(mTarget, -1);
}
}
/**
* 还能否向上滚动
*
* @return
*/
private boolean canChildScrollDown() {
if (mTarget == null) {
return true;
}
if (android.os.Build.VERSION.SDK_INT < 14) {
if (mTarget instanceof AbsListView) {
final AbsListView absListView = (AbsListView) mTarget;
final int lastVisiblePosition = absListView
.getLastVisiblePosition();
final int childIndex = lastVisiblePosition
- absListView.getFirstVisiblePosition();
return absListView.getChildCount() > 0
&& (lastVisiblePosition < absListView.getCount() || absListView
.getChildAt(childIndex).getBottom() > absListView
.getHeight() - absListView.getPaddingTop());
} else {
return mTarget.getScrollY() < mTarget.getHeight() - getHeight();
}
} else {
return ViewCompat.canScrollVertically(mTarget, 1);
}
}
@Override
protected void onFinishInflate() {
// TODO Auto-generated method stub
super.onFinishInflate();
if (getChildCount() > 2) {
throw new IllegalStateException(
"RefreshLinearlayout 只能拥有两个childview");
}
headerView = getChildAt(0);
contentView = getChildAt(getChildCount() - 1);
}
/**
* 设置能滚动的view
*
* @param view
*/
public void setTargetView(View view) {
this.mTarget = view;
}
/**
* 设置固定的view
* @param view
*/
public void setFixedView(View view) {
this.fixedView = view;
}
/**
* 拉长容器
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth = getDefaultSize(0, widthMeasureSpec);
// 一个头一个容器
measureChild(headerView, widthMeasureSpec, heightMeasureSpec);
measureChild(contentView, widthMeasureSpec, heightMeasureSpec);
headerViewHeight = headerView.getMeasuredHeight();
int measuredHeight = getDefaultSize(0, heightMeasureSpec)
+ headerViewHeight;
setMeasuredDimension(measuredWidth, measuredHeight);
if (fixedView != null) {
headerViewHeight -= fixedView.getMeasuredHeight();
}
}
}