Android 进阶 View体系

本文详细介绍了Android中的View坐标系,包括Android坐标系和View坐标系,并展示了如何获取View的宽高和坐标。同时,讲解了MotionEvent的方法,如getX()和getY()。接着,通过自定义View实现滑动效果,使用了layout()、offsetLeftAndRight()、offsetTopAndBottom()和scrollBy()方法。此外,文章还探讨了Scroller在滑动动画中的应用。最后,重点介绍了属性动画,包括ObjectAnimator的各种属性和使用方法,以及如何通过组合动画实现复杂的视图变换效果。

3.View体系和自定义View

3.2坐标系

3.2.1 Android 坐标系

image-20211210201504898

3.2.2 View坐标系

View的坐标系与Android的坐标系并不冲突,它们是共同存在的,它可以用来更好的控制View

img

1.View获取自身的宽高

算出View的宽和高的方法

  int width = button.getRight() - button.getLeft();
  int height = button.getBottom() - button.getTop();

View源码中的 getHight 方法和 getWidth 方法如下:

public final int getHeight(){
			return mBottom - mTop;
}

public final int getWidth(){
			return mRight - mLeft;
}

2.View自身的坐标

  • getTop() : 获取View顶部到父布局顶距离

  • getLeft() : 获取View左部到父布局左距离

  • getRight() : 获取View右部到父布局左距离

  • getBottom() : 获取View底部到父布局底距离

3.MotionEvent是为我们触摸点提供的方法

  • getX():点击事件距离控件左边的距离

  • getY():点击事件距离控件顶边的距离

  • getRawX():点击事件距离父布局左边的距离

  • getRawY():点击事件距离父布局顶边的距离

3.3View的滑动

3.3.1 自定义View 实现移动效果

CustomView

package com.study.c331viewmove;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;

import androidx.annotation.Nullable;

public class CustomView extends View {
    private int lastX;
    private int lastY;

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(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;
                // 方法 1:调用 layout 方法来重新放置它的位置
//                layout(getLeft() + offsetX, getTop() + offsetY,
//                        getRight() + offsetX, getBottom() + offsetY);

                // 方法 2: offsetLeftAndRight() 和 offsetTopAndBottom()
//                offsetLeftAndRight(offsetX);
//                offsetTopAndBottom(offsetY);

                // 方法3: LayoutParams
                //  (1)父控件是 LinearLayout,用 LinearLayout.LayoutParams
                LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
                //  (2)如果父控件是RelativeLayout,用 RelativeLayout.LayoutParams
//                RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
                //  (3)除了使用布局的 LayoutParams 外, 还可以使用 ViewGroup.MarginLayoutParams
//                ViewGroup.MarginLayoutParams layoutParams =  (ViewGroup.MarginLayoutParams) getLayoutParams();
//                layoutParams.leftMargin = getLeft() + offsetX;
//                layoutParams.topMargin = getTop() + offsetY;
//                setLayoutParams(layoutParams);

                // 方法4:scrollBy
                ((View) getParent()).scrollBy(-offsetX, -offsetY);
                break;
        }
        return true;

    }
}

ScrollerCustomView

package com.study.c331viewmove;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Scroller;

import androidx.annotation.Nullable;

public class ScrollerCustomView extends View {
    private Scroller mScroller;

    public ScrollerCustomView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mScroller = new Scroller(context);
    }

    public ScrollerCustomView(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(); // 重绘
        }
    }

    public void smoothScrollTo(int destX, int destY) {
        int scrollX = getScrollX();
        int delta = destX - scrollX;
        mScroller.startScroll(scrollX, 0, delta, 0, 2000);
        invalidate();// 重绘
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <com.study.c331viewmove.CustomView
        android:id="@+id/sv_customview"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_margin="50dp"
        android:background="@android:color/holo_red_light" />

    <com.study.c331viewmove.ScrollerCustomView
        android:id="@+id/scv_customview"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_margin="50dp"
        android:background="@android:color/holo_blue_light" />

</LinearLayout>

translate.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true">
    <translate
        android:duration="1000"
        android:fromXDelta="0"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:toXDelta="300">
    </translate>
</set>

MainActivity.java

package com.study.c331viewmove;

import androidx.appcompat.app.AppCompatActivity;

import android.animation.ObjectAnimator;
import android.os.Bundle;
import android.view.animation.AnimationUtils;

import com.study.c331viewmove.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding mBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(mBinding.getRoot());
        // 设置动画
        // 1.通过 xml 设置动画
//        mBinding.svCustomview.setAnimation(AnimationUtils.loadAnimation(this, R.anim.translate));
        // 2.通过 ObjectAnimator 设置动画
//        ObjectAnimator.ofFloat(mBinding.svCustomview, "translationX",0 , 300).setDuration(1000).start();
        // 3.通过 scrollBy设置位置。
//        mBinding.customview.scrollBy(-50, -50);
        // 4. ScrollerCustomView 通过 Scroller 设置动画
        mBinding.scvCustomview.smoothScrollTo(-400, 0);
    }
}

3.5 属性动画

由于 View 的动画发生后,其相应的位置依然在动画进行前的地方。所以谷歌推出了新的动画框架——AnimatorSet 和 ObjectAnimator。

1.ObjectAnimator

ObjectAnimator animator  =  ObjectAnimator.ofFloat(view, "translationX", 200);
animator.setDuration(300);
animator.start();

属性动画的属性值:

  • translationX 和 translationY:用来沿着 X 轴和 Y 轴进行平移。

  • rotation、rotationX 和 rotationY:用来围绕 View 的支点进行旋转。

  • PrivotX 和 PrivotY:控制 View 对象的支点位置,围绕这个支点进行旋转和缩放变换处理。默认位置是 View 对象的中心位置。

  • alpha:透明度,默认值是1(不透明),0 代表完全透明。

  • x 和 y :描述 View 对象在其容器中的最终位置。

MyView

package com.study.c34animation;

import android.view.View;

public class MyView {
    private View mTarget;

    public MyView(View targer) {
        super();
        mTarget = targer;
    }

    public int getWidth() {
        return mTarget.getLayoutParams().width;
    }

    public void setWidth(int width) {
        mTarget.getLayoutParams().width = width;
        mTarget.requestLayout();
    }
}

MainActivity.java

package com.study.c34animation;

import androidx.appcompat.app.AppCompatActivity;

import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.graphics.Path;
import android.os.Bundle;
import android.util.Log;

import com.study.c34animation.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding mBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(mBinding.getRoot());
        // 1.ObjectAnimator 实现变大显示
        MyView myViewOfSize = new MyView(mBinding.btnSize);
        ObjectAnimator.ofInt(myViewOfSize, "width", 500).setDuration(500).start();
        // 2.ValueAnimator 实现值的变化
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 100);
        valueAnimator.setTarget(mBinding.btnValue);
        valueAnimator.setDuration(5000).start();
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                Float num = (Float) valueAnimator.getAnimatedValue();
                mBinding.btnValue.setText(num.toString());
            }
        });
        // 3.实现透明度变化,并添加监听
        ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.btnAlpha, "alpha", 0, 1.5f);
        animator.setDuration(5000).start();
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {

            }

            @Override
            public void onAnimationEnd(Animator animator) {

            }

            @Override
            public void onAnimationCancel(Animator animator) {

            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });
        // 大部分比较关心 onAnimationEnd 事件
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
            }
        });
        // 4.组合动画
        // X轴移动
        ObjectAnimator animator1 = ObjectAnimator.ofFloat(mBinding.btnAnimatorSet, "translationX", 0.0f, 200.0f, 200.0f);
        // Y轴移动
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(mBinding.btnAnimatorSet, "translationY", 0.0f, 200.0f, 200.0f);
        // 围绕X 来回转90°
        ObjectAnimator animator3 = ObjectAnimator.ofFloat(mBinding.btnAnimatorSet, "rotationX", 0.0f, 90.0f, 0.0f);
        // 按x轴方向比例放大2倍
        ObjectAnimator animator4 = ObjectAnimator.ofFloat(mBinding.btnAnimatorSet, "scaleX", 1.0f, 2.0f);
        // 按Y轴方向比例放大2倍
        ObjectAnimator animator5 = ObjectAnimator.ofFloat(mBinding.btnAnimatorSet, "scaleY", 1.0f, 2.0f);
        AnimatorSet set = new AnimatorSet();
        set.setDuration(1000);
        // 现在的动画是 animator1,先播放 animator3 ,animator1 和 animator2 同时播放,之后在一起播放 animator5
        set.play(animator1).with(animator2).after(animator3).before(animator4);
        set.playTogether(animator4, animator5);// 也可以使用 playTogether 一起播放
        set.start();

        // 5.组合动画2 PropertyValuesHolder 的使用,只能做到多个动画一起执行
        PropertyValuesHolder valuesHolder1 = PropertyValuesHolder.ofFloat("scaleX", 1.0f, 1.5f);
        PropertyValuesHolder valuesHolder2 = PropertyValuesHolder.ofFloat("rotationX", 0.0f, 90.0f, 0.0f);
        PropertyValuesHolder valuesHolder3 = PropertyValuesHolder.ofFloat("alpha", 1.0f, 0.3f, 1.0f);
        ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(mBinding.btnPropertyValuesHolder,
                valuesHolder1, valuesHolder2, valuesHolder3);
        objectAnimator.setDuration(2000).start();

        // 6.使用 xml objectAnimator 实现动画
        Animator animatorXml = AnimatorInflater.loadAnimator(this, R.animator.scale);
        animatorXml.setTarget(mBinding.btnXml);
        animatorXml.start();
    }
}

scale.xml

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:duration="3000"
    android:propertyName="scaleX"
    android:valueFrom="1.0"
    android:valueTo="2.0"
    android:valueType="floatType">
</objectAnimator>

END

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

仰延Elliot

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值