Android 横向带有吸附效果的横向拖动控件(效果同纵向下拉刷新ListView)

来源:互联网 发布:mox反应堆 知乎 编辑:程序博客网 时间:2024/04/30 05:11

先上一张效果图



中间的横向拖动就是我们要做的效果。

一、实现思路

仔细观察不难发现,该拖动view与listview的下拉刷新的效果很类似,手指拖动的时候显示隐藏的view,手指放开自动回弹。只不过区别就是一个横向一个纵向
下拉刷新的实现思路如下:
自定义一个布局继承自LinearLayout,然后在这个布局中加入下拉头和ListView这两个子元素,并让这两个子元素纵向排列。初始化的时候,让下拉头向上偏移出屏幕,这样我们看到的就只有ListView了。然后对ListView的touch事件进行监听,如果当前ListView已经滚动到顶部并且手指还在向下拉的话,那就将下拉头显示出来,松手后进行刷新操作,并将下拉头隐藏。原理如图所示:

我们要实现的效果跟下拉刷新基本一样,只不过是方向是横向而已。如图

二、具体实现

首先我们要先自定义一个LinearLayout,然后在布局中加入隐藏头和剩余部分
布局文件如下:
        <com.boohee.myview.HorizontalDragLinearLayout            android:id="@+id/main_hori_scroll_ll"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:background="@color/white"            android:baselineAligned="false"            android:clickable="true"            android:descendantFocusability="blocksDescendants"            android:orientation="horizontal">            <!--添加一层处理左右拖动时的移动-->            <LinearLayout                android:orientation="horizontal"                android:layout_width="match_parent"                android:layout_height="wrap_content"                >              ...........<pre name="code" class="java"><span style="white-space:pre"></span><!--此处添加 剩余显示的部分-->
</LinearLayout> </com.boohee.myview.HorizontalDragLinearLayout>
自定义的LinearLayout,初始化部分:
    private Context context;    //需要隐藏的view    private TextView headerView;    /**     * 是否已加载过一次layout,这里onLayout中的初始化只需加载一次     */    private boolean loadOnce;    /**     * 需要隐藏View的布局参数     */    private MarginLayoutParams headerLayoutParams;    /**     * 隐藏view的宽度     */    private int hideHeaderWidth;    private float xDown;    /**     * 每次move的x坐标     */    private float tmpXMove;    private float mFocusX = 0.f;    /**     * 在被判定为滚动之前用户手指可以移动的最大值。     */    private int touchSlop;    /**     * 头部回滚的速度     */    public static final int SCROLL_SPEED = -10;    public HorizontalDragLinearLayout(Context context) {        this(context, null);    }    public HorizontalDragLinearLayout(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public HorizontalDragLinearLayout(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        setOrientation(HORIZONTAL);        this.context = context;        init();    }    private void init() {        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();        headerView = new TextView(context);        LinearLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,                ViewGroup.LayoutParams.WRAP_CONTENT);        params.gravity = Gravity.CENTER_VERTICAL;        headerView.setLayoutParams(params);        headerView.setTextColor(Color.BLACK);        headerView.setGravity(Gravity.CENTER);        this.addView(headerView, 0);    }    public void setHeaderText(String string){        if (headerView != null){            headerView.setText(string);        }    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        super.onLayout(changed, l, t, r, b);        if (headerView != null && changed && !loadOnce){            hideHeaderWidth = - headerView.getWidth();            headerLayoutParams = (MarginLayoutParams) headerView.getLayoutParams();            headerLayoutParams.leftMargin = hideHeaderWidth;            headerView.setLayoutParams(headerLayoutParams);            loadOnce = true;        }    }

初始化的时候动态添加了一个TextView,也就是我们需要隐藏的部分
在第一次onLayout布局的时候,我们根据隐藏头的宽度 调整该隐藏头的margin,使其隐藏。

然后我们需要进行拖动识别,重写onTouchEvent 
    @Override    public boolean onTouchEvent(MotionEvent motionEvent) {        //禁止父容器拦截事件        getParent().requestDisallowInterceptTouchEvent(true);        switch (motionEvent.getAction()){            case MotionEvent.ACTION_DOWN:                xDown = motionEvent.getRawX();                return true;            case MotionEvent.ACTION_MOVE:                float xMove = motionEvent.getRawX();                int distance = (int) (xMove - xDown);                if (distance <= 0 && headerLayoutParams.leftMargin <= hideHeaderWidth) {                    return false;                }                if (distance < touchSlop) {                    return false;                }                //大于头部宽度不做处理                if (distance >= -hideHeaderWidth){                    return true;                }                headerLayoutParams.leftMargin = distance + hideHeaderWidth;                headerView.setLayoutParams(headerLayoutParams);                tmpXMove = xMove;                return true;            case MotionEvent.ACTION_UP:                //松手时调用隐藏头部                new HideHeaderTask().execute();                break;            default:                break;        }        return true;    }

由于外层是用的viewPager+fragment进行页面切换,所以默认情况下touch事件是被viewpager消费掉的。我们要组织父控件拦截事件
        getParent().requestDisallowInterceptTouchEvent(true);
其中涉及Android 的touch 分发,可以参考鸿洋大大的这篇博客点击打开链接
在move的时候得到移动的距离,然后对隐藏头设置margin,使其及该LinearLayout的其他子view跟随隐藏头一起向右移动,达到我们想要的效果

up的时候启动一个AsyncTask 动态改变隐藏头的margin,去恢复到最初始的状态,即隐藏头隐藏的状态。

    /**     * 隐藏头部     */    class HideHeaderTask extends AsyncTask<Void, Integer, Integer> {        @Override        protected Integer doInBackground(Void... params) {            int leftMargin = headerLayoutParams.leftMargin;            while (true) {                leftMargin = leftMargin + SCROLL_SPEED;                if (leftMargin <= hideHeaderWidth) {                    leftMargin = hideHeaderWidth;                    break;                }                publishProgress(leftMargin);                sleep(10);            }            return leftMargin;        }        @Override        protected void onProgressUpdate(Integer... leftMargin) {            headerLayoutParams.leftMargin = leftMargin[0];            headerView.setLayoutParams(headerLayoutParams);        }        @Override        protected void onPostExecute(Integer leftMargin) {            headerLayoutParams.leftMargin = leftMargin;            headerView.setLayoutParams(headerLayoutParams);        }    }    private void sleep(int i) {        try {            Thread.sleep(i);        } catch (InterruptedException e) {            e.printStackTrace();        }    }

搞定。

另外 效果中带悬浮进度的progressBar 连接在这里
0 0
原创粉丝点击