自定义View(一)基础知识

一、View


Android 应用中的所有用户界面元素都是使用 View 和 ViewGroup 对象构建而成。View 对象用于在屏幕上绘制可供用户交互的内容。ViewGroup 对象用于储存其他 View(和 ViewGroup)对象,以便定义界面的布局。

Android 提供了一系列 View 和 ViewGroup 子类,可为您提供常用输入控件(如按钮和文本字段)和各种布局模式(如线性布局或相对布局)。

当然,ViewGroup也是View,View是Android中所有控件的基类。

二、Android坐标系


View的位置是由它的四个顶点决定,对应它的四个属性:Left、Top、Right、Bottom

  • Left = getLeft();
  • Top = getTop();
  • Right = getRight();
  • Bottom = getBottom();

3.0后,view增加了新的几个参数:x、y、translationX、translationY;x、y是view的左上角坐标,translationX、translationY是View相对于它的getLeft()、getRight(),也就是它的初始位置的偏移量

  • x = getX();
  • y = getY();
  • translationX = getTranslationX();
  • translationY = getTranslationY();

Left、Top、Right、Bottom四个参数在View的平移过程中不会改变,变的是新增的几个参数,关系如下:

  • x = left + translationX;
  • y = top + translationY;
// View源码
public float getX() {
    return mLeft + getTranslationX();
}

public float getY() {
    return mTop + getTranslationY();
}

三、View的滑动

三、Android中的角度和弧度

数学中的角度制(Degree Measure)

把一个圆周平均分成360份,其中的每一份都是1°的角。这种以“度”作为单位来度量角度单位制叫做角度制。下图是我们常见的180°角度尺。

数学中的弧度制(Radian Measure)

长度为半径长的弧,所对的圆心角是1弧度(Radian),用符号rad表示。

角度和弧度换算
360° = 2π rad
180° = π rad
1° =(π / 180)rad ≈ 0.01745 rad
1 rad =(180 /π)° ≈ 57.30°

α 度的角 = α ·(π / 180)rad

Android中弧度增大的方向:

四、Android中的颜色

颜色模式

颜色定义
1.代码定义

int color = Color.GRAY;     // 灰色

// Color类是使用ARGB值进行表示
int color = Color.argb(127, 255, 0, 0);   // 半透明红色
int color = 0xaaff0000;                   // 带有透明度的红色

2.xml定义

// 定义了红色(没有alpha(透明)通道)
<color name="red">#ff0000</color>
// 定义了蓝色(没有alpha(透明)通道)
<color name="green">#00ff00</color>

颜色引用
1.java代码引用

int color = getResources().getColor(R.color.red);

2.xml引用

<!--在layout文件中引用在/res/values/color.xml中定义的颜色-->
  android:background="@color/red"     

 <!--在layout文件中创建并使用颜色-->
  android:background="#ff0000" 

五、 自定义View常用类

1. MotionEvent :触摸事件

典型的事件类型:

  • ACTION_DOWN : 手指刚接触到屏幕
  • ACTION_UP : 手指从屏幕上抬起时
  • ACTION_MOVE : 手指在屏幕上移动

我们可以通过MotionEvent得到事件发生的坐标位置:

  • getX() : 相对于当前view左上角的x坐标
  • getY() : 相对于当前view左上角的y坐标
  • getRawX() : 相对于手机屏幕左上角的x坐标
  • getRawY() : 相对于手机屏幕左上角的y坐标

可以参照上面的图

2. Configuration :设备配置信息相关

// ----------- Configuration 设备配置信息相关类
Configuration configuration = getResources().getConfiguration();
// 国家码 310
int mcc = configuration.mcc;
// 网络码 260
int mnc = configuration.mnc;
// 横竖屏 1 ORIENTATION_PORTRAIT  2 ORIENTATION_LANDSCAPE,
int orientation = configuration.orientation;

3. ViewConfiguration:UI中超时、大小、距离相关

// ------------ ViewConfiguration UI中超时、大小、距离相关
ViewConfiguration viewConfiguration = ViewConfiguration.get(this);
// 系统可识别最小滑动距离 16
int touchSlop = viewConfiguration.getScaledTouchSlop();
// 是否有物理按键 false
boolean isHasPermanentMenuKey = viewConfiguration.hasPermanentMenuKey();

// 静态方法 有效的双击间隔时间  300
int doubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
// 静态方法 按住变为长按需要的时间  500
int longPressTimeout = ViewConfiguration.getLongPressTimeout();

4. GestureDetector :手势处理工具

当用户触摸屏幕会产生很多手势,我们可以通过View的onTounchEvent()来判断各种手势,但太过麻烦;系统为我们提供了GestureDetector这个类,其中的onTouchEvent(MotionEvent ev)方法替我们判断了各种手势,然后我们再根据它提供的几种接口,只专注于每种手势后续的处理。

其中包括三个接口和一个实现了三个接口的类(空实现):

  • OnGestureListener
    • onSingleTapUp : 点击抬起回调,不能代表是单击事件
    • onLongPress :长按回调,回调后不会再触发其他时间
    • onScroll : 手指滑动时回调
    • onFling : 快速滑动到一定速度松开后继续滑动回调
    • onShowPress : 按住屏幕未移动也未松开时调用,用于告诉用户已经识别按下事件的回调
    • onDown : 按下屏幕时回调
  • OnDoubleTapListener
    • onDoubleTap : 确认是双击事件回调
    • onDoubleTapEvent : 双击后的其他事件回调
    • onSingleTapConfirmed : 确认是单击事件回调
  • OnContextClickListener
    • onContextClick : 鼠标、触摸板,右键点击时回调
  • SimpleOnGestureListener : 实现了以上三个接口

我们可以根据不同的需求实现不同的接口,如果还是觉得需要实现的方法太多,可以继承SimpleOnGestureListener类,选择需要的方法实现

使用如下:
1.创建OnGestureListener的实现类

/**
 * 1. 创建OnGestureListener的实现类
 */
class MyGestureDetectorListener extends GestureDetector.SimpleOnGestureListener{
    // ---------- OnGestureListener 部分
    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        Log.e(TAG,"onSingleTapUp============");
        return super.onSingleTapUp(e);
    }

    @Override
    public void onLongPress(MotionEvent e) {
        Log.e(TAG,"onLongPress============");
        super.onLongPress(e);
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        Log.e(TAG,"onScroll============");
        return super.onScroll(e1, e2, distanceX, distanceY);
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        Log.e(TAG,"onFling============");
        return super.onFling(e1, e2, velocityX, velocityY);
    }

    @Override
    public void onShowPress(MotionEvent e) {
        Log.e(TAG,"onShowPress============");
        super.onShowPress(e);
    }

    @Override
    public boolean onDown(MotionEvent e) {
        Log.e(TAG,"onDown============");
        return super.onDown(e);
    }

    // ----------- OnDoubleTapListener 部分
    @Override
    public boolean onDoubleTap(MotionEvent e) {
        Log.e(TAG,"onDoubleTap============");
        return super.onDoubleTap(e);
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent e) {
        Log.e(TAG,"onDoubleTapEvent============");
        return super.onDoubleTapEvent(e);
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        Log.e(TAG,"onSingleTapConfirmed============");
        return super.onSingleTapConfirmed(e);
    }

    // -------------- OnContextClickListener 部分
    @Override
    public boolean onContextClick(MotionEvent e) {
        Log.e(TAG,"onContextClick============");
        return super.onContextClick(e);
    }
}

2.创建GestureDetector

// 2. 创建GestureDetector
GestureDetector gestureDetector = new GestureDetector(this,new MyGestureDetectorListener());

3.将TounchEvent交给GestureDetector处理

mButton.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // 3. 将TounchEvent交给GestureDetector处理
        return gestureDetector.onTouchEvent(event);
    }
});

《Android开发艺术探索》中建议:如果只是监听滑动相关,建议自己在onTounch中实现;如果是监听单击双击行为,就使用GestureDetector

5. VelocityTracker : 速度跟踪

跟踪触摸事件的速度,用于实现fling或类似的效果

大概使用如下,具体项目中没使用过:

mButton.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                // 1. 创建VelocityTracker,添加追踪事件
                if (mVelocityTracker == null) {
                    mVelocityTracker = VelocityTracker.obtain();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                // 2. 添加要追踪的事件
                mVelocityTracker.addMovement(event);
                // 3. 计算1s内的速度
                mVelocityTracker.computeCurrentVelocity(1000);

                float x = mVelocityTracker.getXVelocity();
                float y = mVelocityTracker.getYVelocity();

                Log.e("VelocityTracker", " x:"+x+" y:"+y );

                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                // 4. 重置回收
                mVelocityTracker.clear();
                mVelocityTracker.recycle();
                mVelocityTracker = null;
                break;
        }
        return false;
    }
});

6. Scroller :弹性滑动

弹性滑动对象,用于view实现弹性滑动。scrollTo()和scrollBy()是瞬间完成滑动,而使用Scroller是在一个时间段完成滑动。

Scroller本身也无法完成View的滑动,它需要和View的computeScroll()方法配合才行。

Scroller、scrollTo()、scrollBy()都是滑动View自身的内容。

Scroller的使用代码基本是固定的,如下:


/**
 * 可滑动内容的LinearLayout
 * @author StriveStay
 * @date 2018/3/15
 */
public class ScrollLinearLayout extends LinearLayout {

    private Scroller mScroller;

    public ScrollLinearLayout(Context context) {
        this(context,null);
    }

    public ScrollLinearLayout(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public ScrollLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 1. 创建Scroller
        mScroller = new Scroller(context);
    }
    
    /**
     * 滚动
     * @param destX x轴滚动距离,负数:内容向x轴正方向移动;正数:内容向X轴负方向移动
     * @param destY y轴滚动距离,负数:内容向Y轴正方向移动;正数:内容向Y轴负方向移动
     */
    public void smoothScrollTo(int destX,int destY){
        // 2. 指定滚动的开始位置,x,y轴上的滚动距离,滚动时间,开始滚动
        mScroller.startScroll(60,60,destX,destY,3000);
        // 3. 调用此方法会重绘,draw()中会调用computeScroll()方法
        invalidate();
    }

    // 4. 重写该方法(用于在其中计算在开始滚动后的持续时间内,该时间点应该滚动的距离)
    @Override
    public void computeScroll() {
        super.computeScroll();
        // getScrollX() View内容相对于View左边缘在X轴的位置,负数:内容向x轴正方向移动;正数:内容向X轴负方向移动
        // getScrollY() View内容相对于View上边缘在Y轴的位置,负数:内容向Y轴正方向移动;正数:内容向Y轴负方向移动
        Log.e("Scroller计算前","getScrollX:"+getScrollX()+"    getScrollY:"+getScrollY()
                +"  getCurrX:"+mScroller.getCurrX()+"   getCurrY:"+mScroller.getCurrY());

        // 5. 计算该时间点应该滚动到的位置,并判断滑动是否完成,ture是未完成
        if(mScroller.computeScrollOffset()){

            Log.e("Scroller计算后","getScrollX:"+getScrollX()+"    getScrollY:"+getScrollY()
                    +"  getCurrX:"+mScroller.getCurrX()+"   getCurrY:"+mScroller.getCurrY());

            // 6. 调用scrollTo(),滚动到计算出的指定位置,会先滚动到第2步中的开始位置
            scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
            // 7. 重绘,继续调用computeScroll()方法,形成循环,直到滚动结束
            invalidate();
        }
    }
}

mScrollLinearLayout.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // 8. 调用滚动方法
        mScrollLinearLayout.smoothScrollTo(-300,0);
    }
});

效果:

我的理解:

7. ViewDragHelper :自定义 ViewGroup 拖拽辅助类

1. Android ViewDragHelper完全解析 自定义ViewGroup神器
2. ViewDragHelper实战 自己打造Drawerlayout

鸿洋大神总结的两篇文章,很好

8. DisplayMetrics :屏幕信息相关

// 第一种方式
// DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
// 第二种方式
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
// 屏幕密度
float density = metrics.density;
// 屏幕密度,以每英寸的像素点数表示
int densityDpi = metrics.densityDpi;
// 屏幕宽度
int widthPixels = metrics.widthPixels;
// 屏幕高度
int heightPixels = metrics.heightPixels;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值