Android View(一)---基础

吐槽

这几天晚上老睡不着,然后只能晚上听极客时间上老师的课程,听着听着就越来越兴奋了233,以后晚上11点半就要熄灯了,自己也要早点睡觉,然后早上起床早点哈,控制好生物钟。

本文思维导图

主要是是看《安卓艺术开发》第三章的学习笔记
好好把安卓的view的基础知识过一遍
在这里插入图片描述

1 View基础知识

主要就是把View里面零碎的知识总结下

1.1 什么是View

学了这么久安卓,突然看到这个问题,感觉无从下手,对我来说,view就是安卓界面上面各种能让别人看到的东西,不论是自己写的,还是系统自带的,都要调用它安卓View

  • View是安卓所有控件的基类
  • Android里所有与用户交互的控件的父类
  • View是界面层的一种抽象,代表一个控件
  • ViewGroup代表控件组,里面包含很多控件,继承View
  • 每一个View都有一个用于绘图的画布,这个画布可以进行任意扩展

1.2 View的位置参数

首先看下坐标系

安卓因为和手机很相关,所以它这块的坐标系也和之前学的数学的坐标系不一样

android的坐标系定义

  • 屏幕的左上角为坐标原点
  • 向右为x轴增大方向
  • 向下为y轴增大方向
    在这里插入图片描述

View的位置描述
下面两点很重要

  • View的位置是相对于父控件而言的
  • View的位置由4个顶点决定的
    在这里插入图片描述
    其中四个顶点分别是
Top = getTop()       //子View上边界到父view上边界的距离
Left = getLeft()     //子View左边界到父view左边界的距离
Bottom = getBottom() //子View下边距到父View上边界的距离
Right = getRight()   //子View右边界到父view左边界的距离

根据上面的图,和四个顶点的,我们也很容易得出View的宽高和坐标的关系

width = right - left
height = bottom - top

看下图也很明显的能得到这块的

然后还有两组方法:

getTanslationX() getTranslationY()

Android3.0之后提供的两个方法,getTranslationX()和getTranslationY(),它们不同于上面的四个参数,这两个参数会由于 View的平移而变化,表示View左上角坐标相对于left、top(原始左上角坐标)的偏移量。
在这里插入图片描述

看图就好了emmmmmm

然后还有最后一组
getX() getY()
Android3.0之后提供了getX()和getY()两个方法。
进去看下源码

/**
 * The visual x position of this view, in pixels. This is equivalent to the
 * {@link #setTranslationX(float) translationX} property plus the current
 * {@link #getLeft() left} property.
 *
 * @return The visual x position of this view, in pixels.
 */
@ViewDebug.ExportedProperty(category = "drawing")
public float getX() {
    return mLeft + getTranslationX();
}

一目了然,很清楚,这个方法就是调用getTanslationX()方法,另一个方法肯定也是这样的哈哈哈

代码是将mLeft加上translationX得到x的,可以看出来,x和y代表的就是当前View左上角相对于父布局的偏移量。
在这里插入图片描述
可以看图很明显得到一个等式

x = left + translationX;
y = top + translationY;

1.3 MotionEvent

在手指接触屏幕之后产生的一系列事件中,典型的事件有下面3种:

  • ACTION_DOWN——手指刚接触屏幕
  • ACTION_MOVE——在屏幕上移动
  • ACTION_DOWN——从屏幕上松开

我们进MotionEvent这个类的源码去看下

public final class MotionEvent extends InputEvent implements Parcelable {
    private static final long NS_PER_MS = 1000000;
    private static final String LABEL_PREFIX = "AXIS_";
    public static final int INVALID_POINTER_ID = -1;
    public static final int ACTION_MASK             = 0xff;
    public static final int ACTION_DOWN             = 0;
    public static final int ACTION_UP               = 1;
    public static final int ACTION_MOVE             = 2;
    public static final int ACTION_CANCEL           = 3;
    ...........................................

进去发现一大堆静态变量emmmmm,别人的代码写的真的舒服整齐,里面也发现了我们上面写的那些

正常操作的情况下,一次手指触摸屏幕的行为会触发一大堆点击事件

  • 点击屏幕后离开松开 DOMN->UP
  • 点击屏幕滑动一段时间再松开,事件序列为 DOWN->MOVE->…MOVE->UP

我们发现这个就是手指在屏幕移动时候发生的,也可以得到手指在滑动的时候的坐标值
可以通过MotionEvent对象调用getX()、getY()、getRawX()、getRawY()获取触碰点的位置参数。

  • getX()、getY() 相对于当前View左上角的x、y值
  • getRawX()、getRawY() 相对于手机屏幕左上角的x、y值。
    我们进去看下这块的源码
public final float getX() {
    return nativeGetAxisValue(mNativePtr, AXIS_X, 0, HISTORY_CURRENT);
}

/**
 * {@link #getY(int)} for the first pointer index (may be an
 * arbitrary pointer identifier).
 *
 * @see #AXIS_Y
 */
public final float getY() {
    return nativeGetAxisValue(mNativePtr, AXIS_Y, 0, HISTORY_CURRENT);
}

好像这块都是调用nativeGetAxisValue方法

然后我们再看下MotionEvent的基本用法

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (mode){
        control1.x = event.getX();
        control1.y = event.getY();
    }else {
        control2.x = event.getX();
        control2.y = event.getY();
    }
    invalidate();
    return true;
}

我们发现这个方法会返回一个boolean的值,但是之前自己好像也没在意过这个值是干嘛的,一直都是自己返回的true,这块好像和View的事件分发有关,自己去网上查了下

true:
1.告诉Android,MotionEvent对象已被使用,不能再提供给其他方法。
2.还告诉Android,继续将此触摸序列的触摸事件(move,up)发送到此方法。

false:
1.告诉Android,onTouch()方法未使用该事件,所以Android寻找要调用的下一个方法。
2.告诉Android。不再将此触摸序列的触摸事件(move,up)发送到此方法。

这块先放在这里,等看到view的事件分发那块我再重新看

1.4 TouchSlop

这个就是安卓里面系统能识别出来的滑动的最小距离,如果手指滑动的距离比这个值少的话,就是系统默认不滑动,感觉这个机制蛮合理的,万一用户轻轻一触碰就触发滑动就很尴尬了

  • 这个是个常量
  • 这个和设备有关
  • 用来过滤滑动距离很少的情况

获取这个值的方式:

int TouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();

然后我们进去看下getScaledTouchSlop()方法

/**
 * @return Distance in pixels a touch can wander before we think the user is scrolling
 */
public int getScaledTouchSlop() {
    return mTouchSlop;
}

然后我们再找啊找

mTouchSlop = TOUCH_SLOP;
private static final int TOUCH_SLOP = 8;

所以,在里面默认的是8dp

在处理滑动的时候可以使用这个值来做一些过滤,过滤掉滑动距离小于这个值,会有更好的用户体验。

1.5 VelocityTracker

这个是获取用户滑动过程中的速度,包括水平速度和竖直速度的,
用法如下

1.先获得一个VelocityTracker对象,然后把时间传入

VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);

2.计算自定义时间内的速度,再调用get获得定义时间内划过的像素点。

velocityTracker.computeCurrentVelocity(1000);
int xV = (int)velocityTracker.getXVelocity();
int yV = (int)velocityTracker.getYVelocity();

3.回收内存

velocityTracker.clear();
velocityTracker.recycle();

注意:

  • 这里的速度就是指一段时间内的手指划过的像素的点的数
  • 手指从右->左 速度为负,反之为正//和坐标系有关
  • 获取速度之前必须要调用computeCurrentVelocity()计算速度。
  • getXVelocity()\getYVelocity()获取到的是计算单位时间内滑过的像素值,并不是速度。

1.5 GestureDetector

手势检测,辅助检测用户的单击,滑动,长按,双击的情况
这里分享一个大佬的博客,里面讲的很清楚
大佬博客地址

我们看下这块整体的结构
在这里插入图片描述
然后我们进入源码看下这个GestureDetector类

public class GestureDetector {

    public interface OnGestureListener {
    boolean onDown(MotionEvent e);//手指轻轻触摸屏幕的瞬间,一个ACTION_DOWN触发

    void onShowPress(MotionEvent e);//手指轻触屏幕,没有松开或挪动

    boolean onSingleTapUp(MotionEvent e);轻触后松开,单击行为,伴随一个ACTION_UP触发

    boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);拖动行为,由一个ACTION_DOWN和一系列ACTION_MOVE触发

    .................
    }
    
    public interface OnDoubleTapListener {
    boolean onSingleTapConfirmed(MotionEvent e);//严格的单击行为,不能是双击中的其中一次单击,onSingleTapUp可以是双击中的其中一次
    boolean onDoubleTap(MotionEvent e);//双击,两次单击,不可能和onSingleTapConfirmed共存

    boolean onDoubleTapEvent(MotionEvent e);
    }//双击行为,双击期间ACTION_DOWN ACTION_MOVE ACTION_UP都会触发此回调。
    public interface OnContextClickListener {
    boolean onContextClick(MotionEvent e);
    }
    ..........................


}

仔细看了下这个类,里面就主要有三个接口,每个接口里面有不同的方法,这些方法就是触摸回调,实现了这些方法,就能实现传入触摸事件之后做出相应的回调

然后我们来看下使用过程:

第一步:目标实现OnGestureListener接口

public class XXXActivity extends Activity implements GestureDetector.GestureListener{
    //下面会有6个方法,开发的时候,不建议直接在Activity中使用,先封装后使用是更好的办法
}

第二步:创建一个GestureDetector对象,并初始化它

private GestureDetector gestureDetector ;
.....
protect void onCreat(Bundle saveInstanceStated){
    ......
    //这样的写法有一个前提,就是这个Activity实现了OnGestureListener接口
    gestureDetecture = new GestureDetector(this,this);
}

第三步:重写目标onTouchEvent(XXX)的方法 Activity中的OnTouchEvent事件交给手势监听器处理:

public boolean onTouchEvent(MotionEvent event){
    return gestureDetector.onTouchEvent(event);
}

2 View的滑动

在安卓里面有很多的滑动的情况,比如下拉,左右滑动切换界面什么的,也是安卓中比较重要的一方面,有三种方法
在这里插入图片描述

2.1 scrollTo/scrollBy

  • 在android中每一个view里都有这两个方法,所以理论上所有的view都是可以滑动的
  • scrollTo是相对于绝对滑动的//就是相对于View的初始位置
  • scrollBy是相对于当前位置的,位置一直是移动的
    我们来看下这块的源码:
public void scrollTo(int x, int y) {
    if (mScrollX != x || mScrollY != y) {
        int oldX = mScrollX;
        int oldY = mScrollY;
        mScrollX = x;
        mScrollY = y;
        invalidateParentCaches();
        onScrollChanged(mScrollX, mScrollY, oldX, oldY);
        if (!awakenScrollBars()) {
            postInvalidateOnAnimation();
        }
    }
}

/**
 * Move the scrolled position of your view. This will cause a call to
 * {@link #onScrollChanged(int, int, int, int)} and the view will be
 * invalidated.
 * @param x the amount of pixels to scroll by horizontally
 * @param y the amount of pixels to scroll by vertically
 */
public void scrollBy(int x, int y) {
    scrollTo(mScrollX + x, mScrollY + y);
}

我们看完这块的源码就可以知道:

  • scrollBy实际上也是调用了scrollTo
  • scorllTo()首先比较内容偏移量和传入的x y是否相等,都不相等再操作。
  • 接着调用了invalidateParentCaches(),方法注释意思是当启动了硬件加速时去通知此View的父容器清除缓存。
  • 调用了onScrollChanged(mScrollX, mScrollY, oldX, oldY),这个方法内部会判断我们是否有设置OnScrollChangeListener,如果有就调用它的回调方法。
  • scrollTo使基于所传参数的绝对滑动(比如:当前坐标是(1,1)所传参数x:2,y:2,最终会滑动到(2,2))
  • scrollBy使基于当前位置的相对滑动(比如:当前坐标是(1,1)所传参数x:2,y:2,最终会滑动到(1+2,1+2))

我们分析下这个过程:
滑动过程中View内部的两个属性mScrollX和mScrollY的改变规则
分别可以通过getScrollX、getScrollY获得

  • scrollTo/scrollBy只是改变了View中内容的位置,并没有改变View的实际位置
  • 在滑动过程中,mScrollX的值总是等于View的左边缘View内容的左边缘的水平距离
  • mScrollY的值总是等于View的上边缘到View内容的上边缘的竖直距离
  • mScrollX/mScrollY单位是像素
  • 从左向右滑动时mScrollX为负数 从上向下滑动时mScrollY为负数

在这里插入图片描述
反正,简单来说逻辑就是改变mScrollX和mScrollY的值,之后刷新UI,显示在新位置。这个滑动不改变View的位置,只是内容的位置

2.2 动画的方式

通过安卓里面的动画让View去平移
第一步:在xml里面定义一个动画集合

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true"
    android:zAdjustment="normal">

    <translate
        android:duration="100"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:toXDelta="100"
        android:toYDelta="100"
        android:interpolator="@android:anim/linear_interpolator"/>

</set>

这个就是让view向右下角移动

再对View对象开始动画,传入加载进来的上面写的动画。

tv.startAnimation(AnimationUtils.loadAnimation(MainActivity.this, R.anim.anim_view_event));

第二步:使用属性动画

ObjectAnimator.ofFloat(tv, "translationX", 0, 10).setDuration(100).start();

注意的地方:

  • View动画是对View影像进行操作,不是真的233
  • 点击事件什么的还是在原来的位置

2.3 改变布局参数

这块很简单,比如我们想把一个Button向右平移100px,我们只需要把这个Button的LayoutParams里面的marginLeft增加100px,类似这种形式

MarginLayoutParams params = (MarginLayoutParams) tv.getLayoutParams();
params.leftMargin += 100;
tv.requestLayout();
//tv.setLayoutParams(params); 也可以使用这个重新设置参数

LayoutParams继承于Android.View.ViewGroup.LayoutParams.
LayoutParams相当于一个Layout的信息包,它封装了Layout的位置、高、宽等信息。假设在屏幕上一块区域是由一个Layout占领的,如果将一个View添加到一个Layout中,最好告诉Layout用户期望的布局方式,也就是将一个认可的layoutParams传递进去。

3 弹性滑动

因为之前我们看到的view的滑动都是特别特别死板那种,不流畅,这样只能算是移动,如果我们要实现滑动的话,我觉得我们就是把一次大的滑动分为很多份小的滑动,每个小的滑动可以看成一次移动

3.1 Scoller

Scroller本身无法实现弹性滑动,需要和View的computeScroll()配合使用。在最后通过分析可以发现也是通过scrollTo()实现滑动的,所以它也是View内容的滑动,而不是View本身的滑动。

 private Scroller mScroller;

public void smoothScroll(int destX, int destY) {
        //画的初始滑动偏移
        int scrollX = getScrollX();
        int scrollY = getScrollY();
        //计算需要滑动的两个方向的大小
        int deltaX = -destX - scrollX;
        int deltaY = -destY - scrollY;
        调用Scroller对象的startScroll()
        mScroller.startScroll(scrollX, scrollY,  deltaX, deltaY, 1000);
        invalidate();//重绘
    }

//固定的重写compuuteScroll
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }
  • 初始化一个Scroller对象
  • 实现computeScroll()方法
  • 自定义滑动内容
  • 刷新
  • 调用

看了下自己之前写的例子

public class CustomView extends View {
    private int lastx;
    private int lasty;
    private Scroller mscroller;
    public CustomView(Context context) {
        super(context);
    }

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

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

    @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 offx = x - lastx;
                int offy = y - lasty;

                //用layout方法重新放置他的位置
              //  layout(getLeft()+offx,getTop()+offy,getRight()+offx,getBottom()+offy);

                //offsetLeftAndRight
                //offsetLeftAndRight(offx);
                //offsetTopAndBottom(offy);

                //LayoutParams 布局参数
//                LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)getLayoutParams();
//                layoutParams.leftMargin = getLeft()+offx;
//                layoutParams.topMargin = getTop()+offy;
//                setLayoutParams(layoutParams);


                //scrollTO和scrollBy
//                ((View)getParent()).scrollBy(-offx,offy);
                
                smoothScrollTo(-lastx,-lasty);


                



                break;
        }
        return true;
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mscroller.computeScrollOffset()){
            ((View)getParent()).scrollBy(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();
    }

}

里面有两个方法要看下

public void startScroll(int startX, int startY, int dx, int dy, int duration) {
    mMode = SCROLL_MODE;
    mFinished = false;
    mDuration = duration;
    mStartTime = AnimationUtils.currentAnimationTimeMillis();
    mStartX = startX;
    mStartY = startY;
    mFinalX = startX + dx;
    mFinalY = startY + dy;
    mDeltaX = dx;
    mDeltaY = dy;
    mDurationReciprocal = 1.0f / (float) mDuration;
}

startScroll()只是进行了一些计算和参数的记录,并没有进行真正的滑动工作。四个参数分别是其实位置的x、y坐标,x、y方向的滑动距离,滑动的时间间隔。

public boolean computeScrollOffset() {
    if (mFinished) {
        return false;
    }

    int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);

    if (timePassed < mDuration) {
        switch (mMode) {
        case SCROLL_MODE:
            final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
            mCurrX = mStartX + Math.round(x * mDeltaX);
            mCurrY = mStartY + Math.round(x * mDeltaY);
            break;
        case FLING_MODE:
            final float t = (float) timePassed / mDuration;
            final int index = (int) (NB_SAMPLES * t);
            float distanceCoef = 1.f;
            float velocityCoef = 0.f;
            if (index < NB_SAMPLES) {
                final float t_inf = (float) index / NB_SAMPLES;
                final float t_sup = (float) (index + 1) / NB_SAMPLES;
                final float d_inf = SPLINE_POSITION[index];
                final float d_sup = SPLINE_POSITION[index + 1];
                velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
                distanceCoef = d_inf + (t - t_inf) * velocityCoef;
            }

            mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
            
            mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
            // Pin to mMinX <= mCurrX <= mMaxX
            mCurrX = Math.min(mCurrX, mMaxX);
            mCurrX = Math.max(mCurrX, mMinX);
            
            mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
            // Pin to mMinY <= mCurrY <= mMaxY
            mCurrY = Math.min(mCurrY, mMaxY);
            mCurrY = Math.max(mCurrY, mMinY);

            if (mCurrX == mFinalX && mCurrY == mFinalY) {
                mFinished = true;
            }

            break;
        }
    }
    else {
        mCurrX = mFinalX;
        mCurrY = mFinalY;
        mFinished = true;
    }
    return true;
}

这个类也很神奇

  • 它首先判断是否完成,如果已经完成就直接返回false。
  • 如果还没完成,计算过去的时间,如果还有剩余,就根据时间百分比计算下一个滑动位置,返回true。
  • 如果已经超过时间,就赋值下一个滑动位置为目标位置,并将mFinished变成true,返回true。
  • 在调用computeScrollOffset()的地方,如果computeScrollOffset()返回了true就进行scrollTo()并重新绘制。

类似于一个差值器

3.2 动画属性

动画本身就是一种渐进的方式

final int startX = 0;
final int deltaX = -100;
final ValueAnimator animator = ValueAnimator.ofInt(0, 1).setDuration(1000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float fraction = animation.getAnimatedFraction();
        tv.scrollTo(startX + (int)(deltaX * fraction), 0);
    }
});

tv.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        animator.start();
    }
});

利用动画的回调,实现像Scroller类似的,在动画改变的时候通过onAnimationUpdate()监听,获得百分比,调用scrollTo()滑动一小步,也是View内容的滑动。

3.3 延时策略

通过发送延时消息从而达到渐近式的效果。可以使用Handler、View的postDelayed()、Thread的sleep()

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值