DragTopLayout

啥也不说,先来个预览图,虽然有点卡:
这里写图片描述

代码地址: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大家留言 -_-

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值