View体系(上)|青训营笔记(一)

简介: View 体系是较为复杂的,但是又非常重要的一个知识点。我们把这部分知识吃透吃熟是十分必要的,打卡第一天,我把View体系的第一部分知识整理出来,快来和我一起学习吧。

1.webp.jpg

这是我参与「第四届青训营 」笔记创作活动的的第1天

View 体系是较为复杂的,但是又非常重要的一个知识点。我们把这部分知识吃透吃熟是十分必要的,打卡第一天,我把View体系的第一部分知识整理出来,快来和我一起学习吧。

View树结构

1.webp.jpg

官方给出我们使用的各种布局和各种 View 都是继承自 ViewGroupView 或者他们的派生类。所以,了解View体系是极其重要的任务

如下图的 View部分继承关系 ,我们可以看到常用的 View 组件、布局组件是如何继承的。

1.webp.jpg

坐标系

学习 View,首先需要知道 View 的位置在 Android 中是如何定义和测量的。

1.webp.jpg

上图之中的蓝色和绿色是有着不同作用含义,我们平时使用也是在不同的地方调用

绿色:在 View 中获得 View 到其父控件之间的距离

蓝色:来自于点击事件 MotionEvent 内部的方法,可以在重写 View 事件分发体系的的三大方法的时候,利用传入的事件调用上图的蓝色方法,获取点击的位置坐标

获取坐标绘制View的滑动

//自定义一个View,点击该View可以随意滑动其位置
//下面有5个方法可以实现,其中两个由于理解为移动的是屏幕框,会使得其他元素一起偏移
public class CoutomView extends View {
    private int lastX;
    private int lastY;
    Scroller mScroller;
    public CoutomView(Context context) {
        super(context);
    }
    public CoutomView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mScroller = new Scroller(context);
    }
    public CoutomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    public CoutomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()){
            ((View)getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
            invalidate();
        }
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - lastX;
                int offsetY = y - lastY;
//                M1 
                layout(getLeft()+offsetX,getTop()+offsetY,
                        getRight()+offsetX,getBottom()+offsetY);
//                M2
//                offsetLeftAndRight(offsetX);
//                offsetTopAndBottom(offsetY);
//                M3
//                ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)getLayoutParams();
//                layoutParams.leftMargin = getLeft()+offsetX;
//                layoutParams.topMargin = getTop()+offsetY;
//                setLayoutParams(layoutParams);
//                M4 会使得其他元素一起偏移
//                ((View)getParent()).scrollBy(-offsetX,-offsetY);
                break;
        }
        return true;
    }
    /**
     * M5
     * 提供给Activity调用滑动,也会使得其他元素一起偏移
     * 
     * @param destX 
     * @param destY
     */
    public void smoothScrollTo(int destX,int destY){
        int scrollX = getScrollX();
        int delta = destX - scrollX;
        mScroller.startScroll(scrollX,0,delta,0,2000);
        invalidate();
    }
}
复制代码

属性动画

区别

View动画:只展示普通动画效果,响应的点击时间的位置依旧在原来的地方。所以无法左交互效果

属性动画:利用属性和对象来控制 View ,同时使得动画执行后可交互

属性动画的执行,可以带上属性以及属性参数

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values)
//使用传入
ObjectAnimator.ofFloat(binding.coutomView,"translationX",0f,300f).setDuration(1000).start()
复制代码

ObjectAnimator

常用属性

  • translationXtranslantionY 沿轴平移
  • rotatianrotatianXrotatianY 沿着某支点进行旋转
  • PrivotXPrivotY 可以控制支点位置,围绕支点旋转和缩放,支点默认为中心位置、
  • alpha 透明度,默认为1,1不透明 0 全透明
  • xy View的终点位置

使用 ObjectAnimator 时,要调用某个属性,该属性需要有对应的 get()set() 方法。若是没有,我们就需要自定义一个属性类或者包装类添加该方法

//MainActivity
val myView = MyView(binding.button)
ObjectAnimator.ofInt(myView,"width",500).setDuration(500).start()
//MyView,给MyView里面的 width 添加一个 set() 和 get() 功能
class MyView(private val mTarget: View) {
    var width: Int
        get() = mTarget.layoutParams.width
        set(width) {
            mTarget.layoutParams.width = width
            mTarget.requestLayout()
        }
}
复制代码

ValueAnimator

这个方法不提供动画效果,类似数值发生器,你需要根据里面的 AnimatorUpdateListener 来监听数值,设置动画变化

//传入的值被 a.animatedValue 获取到,根据该值设置做动画
val animator = ValueAnimator.ofFloat(0f,100f).apply {
            setTarget(binding.button2)
            duration = 1000
            start()
            addUpdateListener { a ->
                val mFloat = a.animatedValue as Float
                binding.button2.rotation = mFloat
                binding.button2.translationX = 100f
            }
        }
//复杂些的动画
binding.button8.setOnClickListener {
            val anim = ValueAnimator.ofFloat(0f, 360f)
            anim.addUpdateListener { animation ->
                val angle = animation.animatedValue as Float
                binding.layer.rotation = angle
                binding.layer.scaleX = 1 + (180 - Math.abs(angle - 180)) / 20f
                binding.layer.scaleY = 1 + (180 - Math.abs(angle - 180)) / 20f
                var shift_x = 500 * Math.sin(Math.toRadians((angle * 5).toDouble())).toFloat()
                var shift_y = 500 * Math.sin(Math.toRadians((angle * 7).toDouble())).toFloat()
                binding.layer.translationX = shift_x
                binding.layer.translationY = shift_y
            }
            anim.duration = 4000
            anim.start()
        }
复制代码

动画的监听

//完整的监听,四个过程
ObjectAnimator.ofFloat(binding.layer,"alpha",1.5f).addListener(object : Animator.AnimatorListener{
            override fun onAnimationStart(p0: Animator?) {
                TODO("Not yet implemented")
            }
            override fun onAnimationEnd(p0: Animator?) {
                TODO("Not yet implemented")
            }
            override fun onAnimationCancel(p0: Animator?) {
                TODO("Not yet implemented")
            }
            override fun onAnimationRepeat(p0: Animator?) {
                TODO("Not yet implemented")
            }
        })
//不完整的监听,匿名类中,重写其中的一个方法
ObjectAnimator.ofFloat(binding.layer,"alpha",0f,1f,0f,1f,0f,1f).apply {
            addListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator?) {
                    super.onAnimationEnd(animation)
                }
            })
            duration = 10000
            start()
            }
复制代码

AnimatorSet 组合动画

我们可以调用 AnimatorSet 里面的 play() 方法,以及该方法内部的类,就可以完成多个动画的组合展示功能

//eg,下面的执行顺序是 a3 -> a2 -> a1
val a1 = ObjectAnimator.ofFloat(binding.coutomView,"translationX",0f,300f,0f)
        val a2 = ObjectAnimator.ofFloat(binding.coutomView,"scaleX",1.0f,2.0f)
        val a3 = ObjectAnimator.ofFloat(binding.coutomView,"rotationX",0.0f,90f,0.0f)
        val set = AnimatorSet().apply {
        duration = 1000
        play(a1).with(a2).after(a3)
        start()
}
复制代码
//简单展示下对应方法的结构
//play()
public AnimatorSet.Builder play(Animator anim){
    if(anim != null) return new Builder(anim);
    return null;
}
//Builder 结构,对应源码可自行查看
public class Builder {
        Builder() {
            throw new RuntimeException("Stub!");
        }
        public AnimatorSet.Builder with(Animator anim) {
            throw new RuntimeException("Stub!");
        }
        public AnimatorSet.Builder before(Animator anim) {
            throw new RuntimeException("Stub!");
        }
        public AnimatorSet.Builder after(Animator anim) {
            throw new RuntimeException("Stub!");
        }
        public AnimatorSet.Builder after(long delay) {
            throw new RuntimeException("Stub!");
        }
    }
复制代码

after(Animator anim) 当下 Builder 的动画放到传入的动画之后执行

after(long delay) 当下 Builder 的动画延迟指定的毫秒执行

before(Animator anim) 当下 Builder 的动画放到传入的动画之前执行

with(Animator anim) 当下 Builder 的动画与传入的动画并行执行

根据这个属性,浅析一下这段代码的逻辑。play(a1).with(a2).after(a3)

  • 首先传入 play() 的是 a1 ,会返回一个含有 a1Builder对象,我们简称这个对象为 b1
  • 再次调用 with() 传入 a2 ,其实就是传入 b1with() 中。那当前动画就是 a1 ,传入的是 a2 ,两个并行执行。最后会返回 this 即为 b1
  • 最后再调用 after() 传入 a3,也还是 b1 内部的 after() 中。即当前动画还是 a1 ,传入的是 a3a1a3 后面执行。最后会返回 this 即为 b1

所以最终的顺序是 :a3 -> a1/a2

由于这几个方法都是同一个对象内的,所以当前动画 currentNode 是不变的,一直都是 a1 。那么其他需要组合的动画,都还会是以 a1 为主题,看是插入到他的前或者后。

如果有两个动画是放置与同一个位置,即 play(a1).after(a2).after(a3) 。那么 a2a3 是并行执行的,即顺序为 a2 /a3 -> a1


相关文章
|
3月前
|
缓存 算法 网络协议
Android面试回忆录移动应用开发专业核心课程
Android面试回忆录移动应用开发专业核心课程
|
3月前
|
算法 程序员 开发者
代码与禅意:技术修炼中的悟性之旅
【5月更文挑战第30天】 在编程世界的林间小径上,每一位开发者都是一位探索者。本文将带你走进程序员的内心世界,透过技术的表象,探讨那些看似无形却能显著提升开发效率和代码质量的“软技能”。从心法到手法,从个人的静心冥想到团队间的默契配合,我们将一探究竟,如何在技术的海洋中找到自己的航向,以及如何让每一行代码都充满“禅意”。
|
11月前
|
消息中间件 NoSQL Java
阿里P7架构师开源分享2023最新897道java面试题答案
一直以来给大家分享的都是技术文章,相信大家该学的技术都已经学习到了,是时候来检验一下子自己的技术深度和广度了,那就是跳槽涨薪面试;转眼间又到了金九银十面试涨薪高峰期,不知道大家有没有准备好呢?
|
运维 架构师 测试技术
架构师成长日记 - 01 4+1视图模型
架构师成长日记 - 01 4+1视图模型
244 0
架构师成长日记 - 01 4+1视图模型
|
异构计算
View体系(下)|青训营笔记
熟悉完 View 的基础,了解完其分发流程,事件分发的传递规则。我们需要深入理解 View 的工作流程,包括绘制原理以及三大方法的流程,洞悉其原理和实现。
View体系(下)|青训营笔记
View体系的一些小问题|青训营笔记
设置点击事件逻辑的时候,最基础的办法就是先用 findViewById() 来绑定实例,其次就是设置一个匿名内部类来监听点击,继而处理事件。
View体系的一些小问题|青训营笔记
|
XML 数据格式
View体系(上)|青训营笔记(二)
View 体系是较为复杂的,但是又非常重要的一个知识点。我们把这部分知识吃透吃熟是十分必要的,打卡第一天,我把View体系的第一部分知识整理出来,快来和我一起学习吧。
View体系(上)|青训营笔记(二)
|
数据可视化
微搭低代码零基础入门课(第六课)
微搭低代码零基础入门课(第六课)
微搭低代码零基础入门课(第六课)
|
前端开发 API
带你封装MVP架构(上)|青训营笔记(二)
我们做一个 MVP 架构的封装,主要其相对于MVC更加解耦,能让开发人员在编写代码的时候更加高效和舒服。
|
JSON 数据处理 Android开发
带你封装MVP架构(下)|青训营笔记(一)
在 Base 类中,我们需要做的就是把每个 Activity 或者 Fragment 等这些组件,或者对应的 MVP 层会用到的基本操作以及联系都编写好。