啥也不说,先来个预览图,虽然有点卡:
代码地址:https://github.com/dreamlizhengwei/DragTopLayout
如果觉得图比较卡,可以搜一下DragTopLayout,git上有一个用的比较广的,附地址:
https://github.com/chenupt/DragTopLayout
这个DragTopLayout比较复杂,而且存在一些bug,比如头部的控件高度是变化的情况下,会存在一些问题,使用时还要借助EventBus,比较麻烦。
综合以上原因,于是乎,自己写了一个,只有一个类,使用非常简单。
下面先介绍一下基本用法:
这里用了我的另一篇博客里的ExpandableTextView
,感兴趣的可以去参考一下
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--DragTopLayout里面放两个Layout分别作为头和内容的跟布局-->
<com.test.dragtoplayout.DragTopLayout
android:id="@+id/drag"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--头的跟布局,里面放头部内容,我这里放的是ExpandableTextView-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.test.dragtoplayout.ExpandableTextView
android:id="@+id/expand_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:text="0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30"
android:textColor="#666666"
android:textSize="16sp" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@android:color/transparent" />
</com.test.dragtoplayout.ExpandableTextView>
</LinearLayout>
<!--内容布局,里面放内容的,我这里放的是一个ListView-->
<LinearLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0f0"
android:orientation="vertical">
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
</LinearLayout>
</com.test.dragtoplayout.DragTopLayout>
</RelativeLayout>
Activity代码:
public class MainActivity extends Activity {
private DragTopLayout drag;
private ListView lv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
drag = (DragTopLayout) findViewById(R.id.drag);
lv = (ListView) findViewById(R.id.lv);
// 设置滚动的view,因为内容不居中可能有好几个view,所以要手动指定谁是可滚动的布局
// 我这里是ListView,还可以是ScrollView等
drag.setTargetView(lv);
lv.setAdapter(new BaseAdapter() {
@Override
public int getCount() {
return 50;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView t = new TextView(MainActivity.this);
t.setText("==================== " +position);
return t;
}
});
}
}
整体使用就是这样,so easy。
下面是DragTopLayout的代码,非常简洁,事件处理这块用到了这篇博客的分析SwipeRefreshLayout的事件处理
代码中的注释还是比较详细的,单独说几点比较关键的:
canChildScrollUp()
方法是判断可滚动View是否能向上滚动,例如ListView是否已经滚动到顶部,是否可以继续向上滚动,这个方法参考了SwipeRefreshLayout
中的判定,是非常权威滴requestDisallowInterceptTouchEvent()
方法也是参考的SwipeRefreshLayout
中的写法,至于为什么,参考SwipeRefreshLayout的事件处理。最后一点就是
onTouchEvent()
的MotionEvent.ACTION_MOVE
事件中的距离计算可能有点不好理解,这个就需要你对自定义View了解的比较深入才能明白了,如果说的比较透彻可能篇幅较大,各位童鞋单独找资料学习吧 = =最后一点,这种效果其实可以借助
CoordinatorLayout
来实现。。。。。。
public class DragTopLayout extends FrameLayout {
public DragTopLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public DragTopLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DragTopLayout(Context context) {
this(context, null);
}
private void init() {
mScroller = new Scroller(getContext().getApplicationContext());
}
/**
* 当前头部可见还是滚出屏幕,true为滚出屏幕
*/
private boolean mCollapsed = false;
/**
* 头部高度
*/
private int mHeadHeight;
/**
* 滚动辅助
*/
private Scroller mScroller;
/**
* 速度计算,每次用完要recycle
*/
private VelocityTracker velocityTracker;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
mHeadHeight = getChildAt(0).getMeasuredHeight();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
View v1 = getChildAt(0);
v1.layout(0, 0, v1.getMeasuredWidth(), v1.getMeasuredHeight());
View v2 = getChildAt(1);
v2.layout(0, v1.getMeasuredHeight(), getMeasuredWidth(),
getMeasuredHeight() + mHeadHeight);
}
/**
* 按下点,滑动过程中的上一个点
*/
private PointF mDownPoint = new PointF();
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownPoint.set(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
float y = event.getY();
// Y方向需要scrollBy的距离,正值表示需要向上滚动
float scrollByDelt = -y + mDownPoint.y;
// 假如按照计算的距离偏移后的偏移量,即getScrollY()的值
float realDelt = getScrollY() + scrollByDelt;
Log.e("lzw", "------> " + scrollByDelt + " " + realDelt);
if (realDelt < 0) { // 表示按照实际的手指滑动距离向下移动的话太大,则直接计算出刚好头部显示出来的realDelt
scrollByDelt = 0 - getScrollY();
} else if (realDelt > mHeadHeight) { // 同样表示实际距离太大,计算出合适的距离
scrollByDelt = mHeadHeight - getScrollY();
}
scrollBy(0, (int) scrollByDelt);
mDownPoint.set(event.getX(), y);
break;
case MotionEvent.ACTION_UP:
mDownPoint.set(0, 0);
checkPosition();
break;
}
return true;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
Log.e("lzw", "onInterceptTouchEvent canChildScrollUp " + canChildScrollUp());
if (canChildScrollUp()) { // 能向上滚动,一定是滚动view处理事件
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownPoint.set(event.getX(), event.getY());
return false;
case MotionEvent.ACTION_MOVE:
// 横向滚动大于纵向,也不响应
if (Math.abs(event.getX() - mDownPoint.x) > Math.abs(event.getY()
- mDownPoint.y)) {
mDownPoint.set(event.getX(), event.getY());
return super.onInterceptTouchEvent(event);
}
// 在向下移动
if (event.getY() > mDownPoint.y) {
mDownPoint.set(event.getX(), event.getY());
if (mCollapsed) { // 头部不可见了,向下滚动需要拦截
return true;
} else {
return super.onInterceptTouchEvent(event);
}
}
// 在向上移动
if (event.getY() < mDownPoint.y) {
mDownPoint.set(event.getX(), event.getY());
if (mCollapsed) { // 头部滚出屏幕,不拦截
return super.onInterceptTouchEvent(event);
} else {
return true;
}
}
mDownPoint.set(event.getX(), event.getY());
case MotionEvent.ACTION_UP:
// 检查头部是否移除去
mCollapsed = getScrollY() >= mHeadHeight;
mDownPoint.set(event.getX(), event.getY());
return super.onInterceptTouchEvent(event);
}
return true;
}
@Override
public void requestDisallowInterceptTouchEvent(boolean b) {
// if this is a List < L or another view that doesn't support nested
// scrolling, ignore this request so that the vertical scroll event
// isn't stolen
if ((android.os.Build.VERSION.SDK_INT < 21 && mTarget instanceof AbsListView)
|| (mTarget != null && !ViewCompat.isNestedScrollingEnabled(mTarget))) {
// Nope.
} else {
super.requestDisallowInterceptTouchEvent(b);
}
}
/**
* @return Whether it is possible for the child view of this layout to
* scroll up. Override this if the child view is a custom view.
*/
public boolean canChildScrollUp() {
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 ViewCompat.canScrollVertically(mTarget, -1)
|| mTarget.getScrollY() > 0;
}
} else {
return ViewCompat.canScrollVertically(mTarget, -1);
}
}
/**
* 检查是否需要关闭或者打开
*/
private void checkPosition() {
// 移出去的大小,不能直接在if里面用,否则返回值不正确
int opOffset = getScrollY();
if (opOffset < (mHeadHeight / 2)) {
open();
} else {
close();
}
}
/**
* 向上移动,隐藏头部
*/
private void close() {
mCollapsed = true;
mScroller.startScroll(0, getScrollY(), 0, mHeadHeight - getScrollY());
invalidate();
}
/**
* 向下移动,头部出现
*/
private void open() {
mCollapsed = false;
mScroller.startScroll(0, getScrollY(), 0, -getScrollY());
invalidate();
}
@Override
public void computeScroll() {
// 返回值为boolean,true说明滚动尚未完成,false说明滚动已经完成。
if (mScroller.computeScrollOffset()) {
// 移动位置
scrollTo(0, mScroller.getCurrY());
invalidate();
}
}
/**
* 可滚动view
*/
private View mTarget;
/**
* 设置可滚动view
*/
public void setTargetView(View v) {
mTarget = v;
}
}
以上,应该没什么大的bug,有bug大家留言 -_-