Android——CoordinatorLayout之Behav

作者: 英勇青铜5 | 来源:发表于2016-11-09 10:40 被阅读931次

    学习资料:

    最近一直在看Java的知识,在简书看到上面两位同学的博客。CoordinatorLayout只是简单使用过一次,也学习了解一下。

    十分感谢两位同学 :)


    1. 简单使用 <p>

    在之前也了解过一点怎么使用CoordinatorLayout,并写了一篇简单入门使用的博客 CoordinatorLayout、Tablayout、Toolbar简单组合使用

    CoordinatorLayout作为一个中间桥梁性质的布局,协调着内部的childView。之前对CoordinatorLayout有点误解,以为需要配合AppBarLayout才有一些比较炫酷的特效,大错特错,BehaviorCoordinatorLayout能够有协调作用以及能支持各种炫酷特效的的关键因素


    dodo_lihao同学的思路很好,博客中也把他自己收集的学习资料整理了出来,就直接看着他的博客,跟着他的思路来学习的,感觉他系列博客中写的案例很容易表现出Behavior的特点,所以思路和代码照搬的dodo_lihao同学的,效果图也就一样了,有点剽窃成果的感觉,哈哈

    运行效果

    依赖控件:红色的MoveView
    绑定控件:蓝色的TextView

    MoevView受手指的控制,手指怎么移动就怎么移动;而绑定的TextView则是由MoveView通过Behavior来控制


    1.1 布局文件中使用 <p>

    布局文件:

    <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        
        <com.szlk.recyclerviewl.view.MoveView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="@color/colorAccent"
            android:gravity="center"
            android:text="MOVE"
            android:textColor="@android:color/white" />
    
        <TextView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="@color/colorPrimary"
            android:gravity="center"
            android:text="@string/coordinator_name"
            android:textColor="@android:color/white"
            app:layout_behavior=".view.LearnBehavior" />
    
    </android.support.design.widget.CoordinatorLayout>
    

    最关键的地方就在于app:layout_behavior,利用这个属性来确定的绑定的目标childView

    指定绑定目标有3种方式:

    1. xml布局通过app:layout_behavior
    2. Java代码中,child.getLayoutParams().setBehavior()来指定
    3. 在目标childView类上,通过@DefaultBehavior来指定

    1.2 MoveView <p>

    MoveView就是一个继承TextView的很简单的自定义View

    public class MoveView extends TextView {
        private float lastX, lastY;
    
        public MoveView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            int action = event.getAction();
            float x = event.getRawX();
            float y = event.getRawY();
            if (action == MotionEvent.ACTION_MOVE) {
                CoordinatorLayout.MarginLayoutParams layoutParams = (CoordinatorLayout.MarginLayoutParams) getLayoutParams();
                //计算当前的左上角坐标
                float left = layoutParams.leftMargin + x - lastX;
                float top = layoutParams.topMargin + y - lastY;
                //设置坐标
                layoutParams.leftMargin = (int) left;
                layoutParams.topMargin = (int) top;
                setLayoutParams(layoutParams);
            }
            lastX = x;
            lastY = y;
            return true;
        }
    }
    

    主要就是重写onTouchEvent()来使MoveView可以根据手指滑动在屏幕改变位置


    1.3 一个简单的自定义Behavior <p>

    LearnBehavior代码:

    public class LearnBehavior extends CoordinatorLayout.Behavior<TextView> {
        private int width, height;
    
        public LearnBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
            DisplayMetrics display = context.getResources().getDisplayMetrics();
            width = display.widthPixels;
            height = display.heightPixels;
        }
    
        /**
         * 绑定
         *
         * @param parent     CoordinatorLayout
         * @param child      使用Behavior的childView,绑定对象
         * @param dependency 依赖的childView
         * @return true 绑定
         */
        @Override
        public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
            return dependency instanceof MoveView;
        }
    
        /**
         * 依赖的childView 发生改变时
         */
        @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency) {
            int top = dependency.getTop();
            int left = dependency.getLeft();
    
            int x = width - left - child.getWidth();
            int y = height - top - child.getHeight();
            Log.e("x,y", "--->" + x + "-->" + y);
            setPosition(child, x, y);
            return true;
        }
    
        /**
         * 设置坐标
         */
        private void setPosition(View v, int x, int y) {
            CoordinatorLayout.MarginLayoutParams layoutParams = (CoordinatorLayout.MarginLayoutParams) v.getLayoutParams();
            layoutParams.leftMargin = x;
            layoutParams.topMargin = y;
            layoutParams.width = y / 2;
            v.setLayoutParams(layoutParams);
        }
    }
    
    

    CoordinatorLayout.Behavior<TextView>这里使用泛型将绑定的childView限制为了TextView,可以根据实际需求来指定类型,也可以直接指定为View

    注意:
    当在布局文件中使用了Behavior后,Behavior代码中确定的交互行为便直接奏效,初始化第一次加载CoordinatorLayout时,使用了BehaviorChildView受到onDependentViewChanged()方法的影响,第一次加载的位置也会受到影响,导致和布局文件中指定的位置不相同

    官方有好几个非常好的学习资料,例如:
    android.support.design.widget.AppBarLayout$ScrollingViewBehavior

    一个依赖AppBarLayout后,处理滑动事件的Behavior,对Behavior中的属性及方法有了大概了解后,可以学习具体细节的设计和优化


    2. Behavior 行为 <p>

    直译就是行为的意思

    源码中的注释:

        /**
         * Interaction behavior plugin for child views of {@link CoordinatorLayout}.
         * 用于CoordinatorLayout中ChildView交互的行为的插件
         *
         * A Behavior implements one or more interactions that a user can take on a child view.
         *一个ChildView可以实现一个或者多个Behavior
         *
         * These interactions may include drags, swipes, flings, or any other gestures.
         *交互的行为包括点击,拖动,滑动或者一些其他的收拾操作
         *
         * @param <V> The View type that this Behavior operates on
         * 泛型就是指定使用当前Behavior的ChildView类型
         */
        public static abstract class Behavior<V extends View> {
            ...
            方法省略
            ...
        }
    

    需要注意的是Behavior可以几乎包括所有的交互行为,配合ViewDragHelper应该能够实现出一些很炫酷的交互效果


    2.1 常用的方法 <p>

    构造方法有两个:

    默认:public Behavior() {}
    
    布局:public Behavior(Context context, AttributeSet attrs) {  }
    

    两个构造方法也比较容易理解,一个是默认的空参的构造方法,一个是带有布局属性AttributeSet的方法,有了这个构造方法,可以直接在布局文件中使用


    根据Behavior的特性,可以将内部的方法分以下类:

    • 测量与布局:
    测量:public boolean onMeasureChild(){}
    布局:public boolean onLayoutChild(){}
    
    • 特定状态:
    //当Behavior添加到参数实例时,回调
    public void onAttachedToLayoutParams(){}
    
    //当Behavior与参数实例分离时,回调
    public void onDetachedFromLayoutParams(){}
    
    //当Behavior关联的对象想要定位到特定的矩形时,回调
    public boolean onRequestChildRectangleOnScreen(){}
    
    //当一个ChildView设置为回避属性时,回调
    public boolean getInsetDodgeRect(){}
    
    //当窗口发生改变时,回调
    public WindowInsetsCompat onApplyWindowInsets(){}
    
    //需要保存临时状态信息,回调
    public Parcelable onSaveInstanceState(){}
    
    //需要恢复临时状态信息,回调
    public void onRestoreInstanceState(){}
    
    //作用未知
    public int getScrimColor(){}
     
    //作用未知
    public float getScrimOpacity(){}
    
    • 确定依赖与绑定对象:
    //根据参数来确定依赖与绑定对象
    public boolean layoutDependsOn(){}
    
    • 当依赖对象发生改变时:
    //当依赖对象发生改变,包括位置,大小,颜色,进行回调
    public boolean onDependentViewChanged(){}
    
    //当依赖对象被移除时,进行回调
    public void onDependentViewRemoved(){}
    
    • 事件相关:
    //拦截事件,在CoordinatorLayout把事件分发到childView之前
    public boolean onInterceptTouchEvent(){}
    
    //消费事件
    public boolean onTouchEvent(){}
    
    • 嵌套滑动:
    //CoordinatorLayout中的滑动嵌套childView开始启动一次嵌套滚动时,回调
    public boolean onStartNestedScroll(){}
    
    //嵌套滑动结束时,回调
    public void onStopNestedScroll(){}
    
    //当一次嵌套滑动被CoordiantorLayout识别并确定时,进行回调
    public void onNestedScrollAccepted(){}
    
    //嵌套滚动正在进行中并且绑定目标childView已经开始滚动或者被CoordinatorLayout接受后试图滚动
    public void onNestedScroll(){}
    
    //嵌套滚动正在准备更新进度,并且是在绑定目标childView已经出现滚动距离之前,回调
    public void onNestedPreScroll(){}
    
    //当嵌套滚动的childView正在开始fling或者一个动作确认为fling
    public boolean onNestedFling(){}
    
    //当滑动嵌套childView检测到适当的条件,马上开始一次fling事件前回调
    public boolean onNestedPreFling(){}
    
    

    暂时就这么分,分类并不算合理,也无所谓,目的是以后自己回头来看时,能比较清晰能快速定位方法是干嘛的


    3. 事件相关 <p>

    需求:CoordinatorLayout内有一个可以点击的TextView,长按之后,可以拖动,此时蓝色的TextView要依然可以点击

    运行效果

    布局代码:

    布局代码中,并没有添加Behaivor,一旦添加了,在加载布局之时,Behaivor便开始作用于依赖目标childView

    <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/cl_coordinator_activity"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <TextView
            android:layout_gravity="center"
            android:id="@+id/tv_coordinator_activity"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="@color/colorPrimary"
            android:gravity="center"
            android:text="@string/coordinator_name"
            android:textColor="@android:color/white" />
    
    </android.support.design.widget.CoordinatorLayout>
    

    Activity代码:

    public class CoordinatorLayoutLActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_coordinator_layout_l);
            init();
        }
    
        /**
         * 初始化
         */
        private void init() {
            initTextView(R.id.tv_coordinator_activity, "TextView-->蓝色被点击");
            CoordinatorLayout layout = (CoordinatorLayout) findViewById(R.id.cl_coordinator_activity);
            layout.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    ToastUtils.show(CoordinatorLayoutLActivity.this, "CoordinatorLayout被点击");
                }
            });
        }
    
        /**
         * TextView进行初始化
         */
        private void initTextView(int id, final String str) {
            final TextView tv = (TextView) findViewById(id);
            tv.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    ToastUtils.show(CoordinatorLayoutLActivity.this, str);
                }
            });
    
            /**
             *  长按 ,提示动画效果结束后 ,动态添加 Behavior
             */
            tv.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    //动画提示效果
                    animation(tv);
                    return true;
                }
            });
        }
    
        private void animation(final TextView tv) {
            AnimatorSet set = new AnimatorSet();
            set.setInterpolator(new BounceInterpolator());
            set.setDuration(1000);
            set.playTogether(
                    ObjectAnimator.ofFloat(tv, "scaleX", 1, 1.5f),
                    ObjectAnimator.ofFloat(tv, "scaleY", 1, 1.5f),
                    ObjectAnimator.ofFloat(tv, "scaleX", 1.5f, 1),
                    ObjectAnimator.ofFloat(tv, "scaleY", 1.5f, 1)
            );
            //动画监听,结束时添加 Behavior
            set.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    addBehavior(tv);
                }
            });
            set.start();
        }
    
        /**
         * 为TextView添加Behavior
         */
        private void addBehavior(TextView tv) {
            tv.setLongClickable(false);
            CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) tv.getLayoutParams();
            //为TextView设置Behaior
            lp.setBehavior(new LongBehavior());
            ToastUtils.show(CoordinatorLayoutLActivity.this, "可以开始拖动了");
        }
    }
    
    

    代码很简单,都是一眼能看明白的


    LongBehavior代码:

    
        private float lastX, lastY;
        private float moveX, moveY;
    
        public LongBehavior() {
            Log.e("LongBehavior", "新建");
        }
    
        public LongBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
    
        @Override
        public boolean onInterceptTouchEvent(CoordinatorLayout parent, TextView child, MotionEvent ev) {
            int action = ev.getAction();
            boolean isIntercept = false;
            float x = ev.getX();
            float y = ev.getY();
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    lastX = x;
                    lastY = y;
                    //判断落点是否在TextView范围内
                    //若不在 就进行拦截 返回true
                    isIntercept = !isInChildView(child, ev);
                    if (isIntercept) {
                        Log.e("MotionEvent.ACTION_DOWN", "---->MotionEvent.ACTION_DOWN--->进行拦截");
                    } else {
                        Log.e("MotionEvent.ACTION_DOWN", "---->MotionEvent.ACTION_DOWN--->不拦截");
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                     //滑动距离大于10
                    if (Math.abs(lastX - x) >= 10 || Math.abs(lastY - y) >= 10) {
                        isIntercept = true;
                    }
                    break;
            }
            return isIntercept;
        }
    
        /**
         * 判断落点是否在childView范围内
         */
        private boolean isInChildView(TextView child, MotionEvent ev) {
            return ev.getX() >= child.getLeft() && ev.getX() <= child.getRight()
                    && ev.getY() >= child.getTop() && ev.getY() <= child.getBottom();
        }
    
        @Override
        public boolean onTouchEvent(CoordinatorLayout parent, TextView child, MotionEvent ev) {
            //根据是否拦截来执行
            if (onInterceptTouchEvent(parent, child, ev)) {
                int action = ev.getAction();
                float x = ev.getX();
                float y = ev.getY();
                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                        moveX = x;
                        moveY = y;
                        break;
                    case MotionEvent.ACTION_MOVE:
                        //计算偏移量
                        float offsetX = x - moveX;
                        float offsetY = y - moveY;
                        // Log.e("offset", "&&&--" + offsetX + "-->" + offsetY);
                        if (Math.abs(offsetX)>= 10 || Math.abs(offsetY) >= 10) {
                            CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
                            layoutParams.leftMargin = (int) (offsetX);
                            layoutParams.topMargin = (int) (offsetY);
                            child.setLayoutParams(layoutParams);
                        }
                        break;
                }
            }
            return true;
        }
    }
    

    当拦截了DOWN事件之后,后续的事件便都由CoordinatorLayout来消费,onTouchEvent返回了True,事件也就终止了,onClik便也接收不到事件了,CoordinatorLayout自身的点击事件不能执行了,


    4.最后 <p>

    嵌套滚动事件,下一篇进行记录学习

    本人很菜,有错误请指出

    共勉 :)

    相关文章

      网友评论

      • GG_Jin:这样的话 是不是长按一次以后 不需要长按就可以移动了啊
      • 吉凶以情迁:看不懂怎么办
        英勇青铜5: @情随事迁666 啊,我写的也挺凌乱的,当时我自己这么学,就这么写了
      • dodo_lihao:写得很好,很多值得学习的地方。
        英勇青铜5:@dodo_lihao :smile: :smile: :smile:
        dodo_lihao:@英勇青铜5 没有,可以看到你很多思路和想法在里面。有很多方法含义的补充的。也学习到你很多。互相学习!
        英勇青铜5:@dodo_lihao :scream: :scream: 抄的你的博客。。。。

      本文标题:Android——CoordinatorLayout之Behav

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