美文网首页UI
DragLayout可拖拉的自定义view

DragLayout可拖拉的自定义view

作者: dafaycoding | 来源:发表于2016-12-19 12:00 被阅读179次

先上优雅又飘逸的运行效果

darylayout_1.gif

之前做题库项目的时候,有一个这样的需求,当题目是材料题的时候,上面是大段的文字,下面是几个选择题,这就要求上面的材料部分可以上下滚动查看,下面的选择题可以左右滑动查看,所以需要这样的自定义view。

实现前的分析

  1. 要想同时改变上面部分和下面部分的高度,就不能用DrawLayout那样的侧滑View,因为DrawLayout底部和上面是互不影响的两层。
  2. 外层布局我用RelativeLayout,然后通过LayoutParams设置上面部分的高度,下面部分的即为外层RelativeLayout的高度减去上面部分高度
  3. 通过拖动中间的箭头图标来改变上面部分的高度。

使用方式

DragLayout所用到的布局

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <RelativeLayout
        android:id="@+id/rl_container_top"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </RelativeLayout>
    
    <ImageView
        android:id="@+id/iv_thumb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/rl_container_bottom"
        android:layout_centerHorizontal="true"
        android:scaleType="fitStart"
        android:src="@mipmap/question_slide_stick_layout"
        android:visibility="gone" />

    <RelativeLayout
        android:id="@+id/rl_container_bottom"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/rl_container_top"
        android:layout_marginTop="0dp"
        android:background="#f8f8f8"></RelativeLayout>
</merge>

DrayLayout用到的attrs

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="DragLayout">

        <attr name="layout_top" format="reference" />
        <attr name="layout_bottom" format="reference" />
        <attr name="questionType" format="enum">
            <enum name="choice" value="1" />
            <enum name="material" value="2" />
        </attr>

    </declare-styleable>

</resources>

由于DrayLayout的代码比较少,这里就直接粘了出来

package com.example.idea.draglayout.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;

import com.example.idea.draglayout.R;
import com.example.idea.draglayout.utils.DpUtils;
import com.example.idea.draglayout.utils.LogUtils;


/**
 * Created by Deemo on 15/10/16.
 */
public class DragLayout extends RelativeLayout {

    //默认高度
    private final int DEFAULT_HEIGHT_DP = 240;

    private RelativeLayout mRlContainerTop;
    private RelativeLayout mRlContainerBottom;
    private ImageView mIvThumb;
    private int resTop, resBottom;
    private int type;
    private Context mContext;

    //触发移动事件的最短距离,如果小于这个距离就不触发移动控件,如viewpager就是用这个距离来判断用户是否翻页
    private int mTouchSlop;
    private float mDownX, mDownY, mMoveY;

    private boolean isTouchThumb;
    private boolean isTendToMove;

    private float mThumbMinY;

    private int mBottomContentViewHeight;
    private int mLastBottomTop;


    public DragLayout(Context context) {
        this(context, null);
    }

    public DragLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        //自定义控件的属性
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DragLayout, defStyleAttr, 0);
        resTop = array.getResourceId(R.styleable.DragLayout_layout_top, 0);
        resBottom = array.getResourceId(R.styleable.DragLayout_layout_bottom, 0);
        setType(array.getInteger(R.styleable.DragLayout_questionType, 0));
        array.recycle();

        init(context);
    }

    private void init(Context context) {
        mContext = context;
        LayoutInflater.from(context).inflate(R.layout.view_draglayout, this, true);
        mRlContainerTop = (RelativeLayout) findViewById(R.id.rl_container_top);
        mRlContainerBottom = (RelativeLayout) findViewById(R.id.rl_container_bottom);
        mIvThumb = (ImageView) findViewById(R.id.iv_thumb);
        setTopAndBottomLayout(resTop, resBottom);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        initHeight();
    }

    private void initHeight() {
        float defaultHeight = DpUtils.dp2px(mContext.getResources(), DEFAULT_HEIGHT_DP);
        //通过LayoutParams来设置上面部分的高度
        ViewGroup.LayoutParams layoutParams = mRlContainerTop.getLayoutParams();
        layoutParams.height = (int) defaultHeight;
        mRlContainerTop.setLayoutParams(layoutParams);
    }

    public void setType(int type) {
        this.type = type;
    }

    public void setTopLayout(View view) {
        if (view != null) {
            mRlContainerTop.addView(view);
        }
    }


    public void setBottomLayout(final View view) {
        if (view != null) {
            mRlContainerBottom.addView(view);
            mIvThumb.setVisibility(View.VISIBLE);

            if (view.getMeasuredHeight() == 0) {
                view.measure(0, 0);
            }
            mBottomContentViewHeight = view.getMeasuredHeight();

        } else {
            mRlContainerTop.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
            mRlContainerBottom.setVisibility(View.GONE);
            mIvThumb.setVisibility(View.GONE);
        }
    }

    public void setTopAndBottomLayout(int top, int bottom) {
        if (top != 0) {
            View topView = LayoutInflater.from(getContext()).inflate(top, null);
            setTopLayout(topView);
        }

        if (bottom != 0) {
            View bottomView = LayoutInflater.from(getContext()).inflate(bottom, null);
            setTopLayout(bottomView);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        LogUtils.d(this, String.format("DragLayout w=%d, h=%d, ow=%d, oh=%d", w, h, oldw, oldh));
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownX = ev.getX();
                mDownY = ev.getY();
                mMoveY = mDownY;
                isTouchThumb = isTouchThumb();
                if (isTouchThumb())
                    return true;
                return super.dispatchTouchEvent(ev);
            case MotionEvent.ACTION_MOVE:
                float moveY = ev.getY();
                if (isTouchThumb) {
                    float movedY = moveY - mMoveY;
                    if (!isTendToMove) {
                        isTendToMove = isTendToMove(movedY);
                    }
                    if (isTendToMove) {
                        tryToMoveThumb(movedY);
                        mMoveY = moveY;
                        return true;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                reset();
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    private void reset() {
        isTendToMove = false;
    }

    /**
     * 拖动图片的有效按压部分
     */
    private boolean isTouchThumb() {
        RectF rect = new RectF(mIvThumb.getLeft(), mIvThumb.getTop(), mIvThumb.getRight(), mIvThumb.getBottom());
        return rect.contains(mDownX, mDownY);
    }

    private boolean isTendToMove(float y) {
        return Math.abs(mDownY - y) >= mTouchSlop;
    }

    /**
     * 拖动范围限制,拖动的图片不能划出界面
     *
     * @param movedY
     */
    private void tryToMoveThumb(float movedY) {
        boolean canMove = (mIvThumb.getBottom() + movedY) < getHeight() && (mIvThumb.getTop() + movedY) > mThumbMinY;
        if (canMove) {
            mIvThumb.offsetTopAndBottom((int) movedY);
            adjustTopAndBottom((int) movedY);
            mLastBottomTop = mRlContainerBottom.getTop();
        }
    }

    /**
     * 改变上面部分高度
     *
     * @param movedY
     */
    public void adjustTopAndBottom(int movedY) {
        ViewGroup.LayoutParams layoutParams = mRlContainerTop.getLayoutParams();
        layoutParams.height += movedY;
        if (layoutParams.height < 0) {
            layoutParams.height = 0;
        }
        mRlContainerTop.setLayoutParams(layoutParams);

        LogUtils.d(this, "adjustTopAndBottom" + movedY + ", mRlytContainerTop=" + mRlContainerTop);
    }


    protected final int getBottomContentViewHeight() {
        return mBottomContentViewHeight;
    }

    protected int getBottomTop() {
        return mLastBottomTop;
    }

}

存在问题

由于动态改变LayoutParams的高度,导致TopLayout和BottmoLayouy的子view的onMeasure()和onLayout方法不断执行,从而在2.3版本性能不好的安卓手机上测试,会有卡顿感觉。

ok,基本完工了,后期如果有了更好的实现方式再来更新。


源码地址 https://github.com/idea007/DragLayout

相关文章

  • DragLayout可拖拉的自定义view

    先上优雅又飘逸的运行效果 之前做题库项目的时候,有一个这样的需求,当题目是材料题的时候,上面是大段的文字,下面是几...

  • 使用ViewDragHelper打造属于自己的DragLayou

    使用ViewDragHelper打造属于自己的DragLayout(抽屉开关 ) DrawLayout这个自定义的...

  • 自定义刻度盘View--详解

    简介 本篇是接上一篇seekbar的自定义view进阶版。本自定义view主要功能: 可自定义起始时间以及最大时间...

  • Android View(转)

    自定义View的原理自定义View基础 - 最易懂的自定义View原理系列自定义View Measure过程 - ...

  • IOS实战 (3) 之 水平 循环滚动文本

    实现效果## 可以将 Label 变成自定义的 View可 滚动播放 View 实现思路## 1.UIScroll...

  • 自定义View5---完整的自定义View

    移步自定义View系列 1.自定义view的分类自定义单一view(不含子view)继承view继承特定view如...

  • 自定义View系列

    自定义View1---知识储备自定义View2---View Measure过程自定义View3---View L...

  • DragLayout

    类似ScrollView的写法,可实现子View的拖拽及复位到边界功能 效果图 使用方式 源代码 求Star

  • 自定义view

    Android自定义View 为什么要自定义View自定义View的基本方法 自定义View的最基本的三个方法分别...

  • 自定义View

    自定义View系列文章 自定义View view向上滚动

网友评论

    本文标题:DragLayout可拖拉的自定义view

    本文链接:https://www.haomeiwen.com/subject/szctvttx.html