View与ViewGroup

1. ViewViewGroup的关系

    ViewAndroid所有控件的基类,我们平常用的布局控件 LinearLayout,它继承自 ViewGroupViewGroup 可以理解为 View的组合,它可以包含很多 View 以及ViewGroup。如下图:
在这里插入图片描述
需要注意的是ViewGroup也继承自ViewViewGroup派生了多种布局控件子类,比如LinearLayoutRelativeLayout等。

2. 坐标获取

在这里插入图片描述

  • getTop(),获取View自身顶边到其父布局顶边的距离;
  • getLeft(),获取View自身左边到其父布局左边的距离。
  • getRight(),获取View自身右边到其父布局左边的距离。
  • getBottom(),获取View自身底边到其父布局顶边的距离。
  • View获取自身宽度:width = getRight() - getLeft();。当然系统提供了getWidth()来直接获取;
  • View获取自身高度:height=getBottom() - getTop();。同样的系统提供了getHeight()来直接获取;
  • 触控事件中,使用getRawX()和触控事件中,使用getRawX()getRawY()方法获得的坐标也是
    Android坐标系的坐标。getRawY()方法获得的坐标也是Android坐标系的坐标。
  • 在触摸事件中,使用onTouchEvent(MotionEvent event)方法来处理,MotionEvent也提供了获取焦点坐标的各种方法。
  • getX():获取点击事件距离控件左边的距离,即视图坐标。
  • getY():获取点击事件距离控件顶边的距离,即视图坐标。
  • getRawX():获取点击事件距离整个屏幕左边的距离,即绝对坐标。
  • getRawY():获取点击事件距离整个屏幕顶边的距离,即绝对坐标。

3. View的滑动

View的滑动是Android实现自定义控件的基础,同时在开发中我们也难免会遇到View的滑动处理。其实不管是哪种滑动方式,其基本思想都是类似的:当点击事件传到View时,系统记下触摸点的坐标,手指移动时系统记下移动后触摸的坐标并算出偏移量,并通过偏移量来修改View的坐标。实现View滑动有很多种方法,在这里主要讲解6种滑动方法,分别是layout()offsetLeftAndRight()
offsetTopAndBottom()LayoutParams、动画、scollToscollBy,以及Scroller

3.1 案例layout()

首先自定义View,如:

package com.example.myapplication;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.Nullable;

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

    public MyView(Context context) {
        super(context);
    }

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

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

    // 复写onTouchEvent,以使用坐标
    @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;
                // 使用layout方法来重新放置它的位置
                layout(getLeft()+offsetx, getTop()+offsety, getRight()+offsetx, getBottom()+offsety);
                break;
        }
        return true;
    }
}

然后,在主布局文件中使用即可,如:

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

    <com.example.myapplication.MyView
        android:id="@+id/myView"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:background="@color/colorPrimary"
        />
</LinearLayout>

效果:
在这里插入图片描述
拖动可移动。

3.2 案例offsetLeftAndRight & offsetTopAndBottom

case MotionEvent.ACTION_MOVE:
	int offsetx = x - lastX;
	int offsety = y - lastY;
	// 使用offsetLeftAndRight & offsetTopAndBottom
	offsetLeftAndRight(offsetx);
	offsetTopAndBottom(offsety);
	break;

3.3 案例LayoutParams(改变布局参数)

LayoutParams主要保存了一个View的布局参数,因此我们可以通过LayoutParams来改变View的布局参数从而达到改变View位置的效果。

case MotionEvent.ACTION_MOVE:
	int offsetx = x - lastX;
	int offsety = y - lastY;
	// 使用
	LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
	layoutParams.leftMargin = getLeft() + offsetx;
	layoutParams.topMargin = getTop() + offsety;
	setLayoutParams(layoutParams);
	break;

这里使用LinearLayout.LayoutParams的原因是主文件我们使用的布局是LinearLayout。如果父控件是RelativeLayout,则要使用RelativeLayout.LayoutParams。还可以使用ViewGroup.MarginLayoutParams来实现:

case MotionEvent.ACTION_MOVE:
	int offsetx = x - lastX;
	int offsety = y - lastY;
	// 使用
	ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
	layoutParams.leftMargin = getLeft() + offsetx;
	layoutParams.topMargin = getTop() + offsety;
	setLayoutParams(layoutParams);
	break;

3.4 动画

可以使用动画来进行移动。
布局文件不变,然后删除自定义控件中的onTouchEvent方法,在res文件下新建anim文件夹,在其下新建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:toXDelta="300"/>
</set>

属性android:fillAfter="true"用于设置运动后停留在该位置。
MainActivity.java中设置关联,即:

public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private MyAdapter adapter;
    private List<String> mList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.recycleview);
        MyView myView = findViewById(R.id.myView);
        // 设置关联,加载定义的动画
        myView.setAnimation(AnimationUtils.loadAnimation(this, R.anim.translate));
    }
}

    需要注意的是,View动画并不能改变View的位置参数。如果对一个Button进行如上的平移动画操作,当Button平移300像素停留在当前位置时,我们点击这个Button并不会触发点击事件,但在我们点击这个Button的原始位置时却触发了点击事件。在Android 3.0时出现的属性动画解决了上述问题:

public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private MyAdapter adapter;
    private List<String> mList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.recycleview);
        MyView myView = findViewById(R.id.myView);
        // 从0变化到300
        ObjectAnimator.ofFloat(myView, "translationX", 0, 300).setDuration(1000).start();
    }
}

关于属性动画,这里做一个简单的介绍

3.4.1 属性动画

Android中动画分类:

  • 帧动画
  • View动画,它提供了AlphaAnimationRotateAnimationTranslateAnimationScaleAnimation4种动画方式,并提供了AnimationSet动画集合来混合使用多种动画。但不具有交互性,即:当某个元素发生View动画后,其响应事件的位置依然在动画进行前的地方,所以View动画只能做普通的动画效果,要避免涉及交互操。但是它的优点也非常明显:效率比较高,使用也方便
    作。
  • 属性动画,由Android 3.0推出。Animator框架中使用最多的就是AnimatorSetObjectAnimator配合:使用 ObjectAnimator 进行更精细化的控制,控制一个对象和一个属性值,而使用多个ObjectAnimator组合到AnimatorSet形成一个动画。属性动画通过调用属性getset方法来真实地控制一个View的属性值,因此,强大的属性动画框架基本可以实现所有的动画效果

创建一个 ObjectAnimator 只需通过其静态工厂类直接返还一个ObjectAnimator对象。参数包括一个对象和对象的属性名字,但这个属性必须有getset方法,其内部会通过Java反射机制来调用set方法修改对象的属性值。
下面就是一些常用的可以直接使用的属性动画的属性值。

  • translationXtranslationY:用来沿着X轴或者Y轴进行平移。
  • rotationrotationXrotationY:用来围绕View的支点进行旋转。
  • PrivotXPrivotY:控制View对象的支点位置,围绕这个支点进行旋转和缩放变换处理。默认该支点
    位置就是View对象的中心点。
  • alpha:透明度,默认是1(不透明),0代表完全透明。
  • xy:描述View对象在其容器中的最终位置。

如果一个属性没有getset方法,也可以通过自定义一个属性类或包装类来间接地给这个属性增加getset方法。如:
自定义view:

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

    public MyView(Context context) {
        super(context);
    }

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

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

对应布局文件:

<?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">

    <com.example.myapplication.MyView
        android:layout_height="50dp"
        android:layout_width="50dp"
        android:id="@+id/myView"
        android:background="@color/colorPrimary"
        />

</LinearLayout>

主文件:

public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private MyAdapter adapter;
    private List<String> mList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyView myView = findViewById(R.id.myView);
        PackageView packageView = new PackageView(myView);
        ObjectAnimator.ofInt(packageView, "width", 300).setDuration(1000).start();
    }
	// 包装类
    public class PackageView{
        private View view;
        PackageView(View view){
            Log.d("HELLO", "PackageView: "+Boolean.toString(view==null));
            this.view = view;
        }
        public int getWidth(){
            return view.getLayoutParams().width;
        }
        public void setWidth(int width){
            Log.d("HELLO", "setWidth: "+Boolean.toString(this.view==null));
            view.getLayoutParams().width = width;
            // 重新绘制View
            view.requestLayout();
        }
    }
}

最终效果,宽度缓慢扩充到300
在这里插入图片描述

组合动画 AnimatorSet

上面的ObjectAnimator针对单个属性动画,往往动画比较复杂,不单单涉及一个属性。就需要使用组合动画。
AnimatorSet.Builder中包括以下4个方法。

  • after(Animator anim):将现有动画插入到传入的动画之后执行。
  • after(long delay):将现有动画延迟指定毫秒后执行。
  • before(Animator anim):将现有动画插入到传入的动画之前执行。
  • with(Animator anim):将现有动画和传入的动画同时执行。

如:

public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private MyAdapter adapter;
    private List<String> mList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyView myView = findViewById(R.id.myView);

        ObjectAnimator animator1 = ObjectAnimator.ofFloat(myView, "translationX", 300);
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(myView, "scaleX", 1.0f, 2.0f);
        AnimatorSet set = new AnimatorSet();
        set.setDuration(1000);
        set.play(animator1).after(animator2);
        set.start();
    }
}

View动画一样,属性动画也可以直接写在XML中。

XML中定制属性动画

前面提到了View也可以指定XML动画,但是其改变后点击事件还是在原来位置上,故而这里的XML属性动画还是ObjectAnimator版本。
同样的,在animator中定义test.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="1000"
    android:propertyName="scaleX"
    android:valueFrom="1.0"
    android:valueTo="3.0"
    android:valueType="floatType"
    >
</objectAnimator>

主文件:

public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private MyAdapter adapter;
    private List<String> mList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyView myView = findViewById(R.id.myView);
        myView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this, "Hello", Toast.LENGTH_SHORT).show();
            }
        });
        Animator animator = AnimatorInflater.loadAnimator(this, R.animator.test);
        animator.setTarget(myView);
        animator.start();
    }
}

3.5 scrollToscollBy

scrollTo(x,y)
表示移动到一个具体的坐标点,而scrollBy(dx,dy)则表示移动的增量为dxdy

case MotionEvent.ACTION_MOVE:
	int offsetx = x - lastX;
	int offsety = y - lastY;
	// 使用
	((View)getParent()).scrollBy(-offsetx, -offsety);
	break;

内容参考:

  • 《Android进阶之光》
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

梦否

文章对你有用?不妨打赏一毛两毛

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

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

打赏作者

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

抵扣说明:

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

余额充值