El flujo básico de la vista personalizada

A veces necesitamos algunos efectos o funciones especiales, pero los controles del sistema no pueden satisfacer nuestras necesidades. En este momento, necesitamos definir un control nosotros mismos.

Proceso de vista personalizado

Heredar vista

Para personalizar View, primero debe heredar View o sus subclases. Si el efecto que necesita lograr es más complicado, generalmente necesita heredar View. A veces, lo que necesitamos son controles del sistema más algunos efectos especiales, entonces puede heredar las subclases de View. (como TextView)

Si desea diseñar un diseño usted mismo o combinar otros controles, debe heredar el diseño que viene con ViewGroup o LinearLayout, FrameLayout y otros sistemas

anular constructor

La vista personalizada debe anular al menos dos métodos de construcción

  • El constructor utilizado para crear controles directamente en código Java
public CustomView(Context context) {
        this(context, null);
}
  • Defina el constructor utilizado por View en el archivo xml
public CustomView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
}

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

Atributos en xml personalizado

Primero, debe crear un nuevo res/values/custom_view_attrs.xml y declararlo de la siguiente manera

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CustomView">
        <attr name="custom_width" format="dimension" />
    </declare-styleable>
</resources>

Luego puede declararlo en el archivo de diseño 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"
    tools:context="cn.lkllkllkl.customview.MainActivity">

    <cn.lkllkllkl.customview.CustomView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:custom_width="100dp"/>

</LinearLayout>

Luego podemos sacar el valor de esta propiedad y usarlo en el constructor de la Vista personalizada

public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr){
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
        float customWidth = 0;
        TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
        customWidth = typeArray.getDimension(R.styleable.CustomView_custom_width, 100);
        // 需要记得回收
        typeArray.recycle();
}

método onMeasure

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec);    

MedidaSpec

Para comprender los dos parámetros de onMeasure aquí, primero debe comprender la clase MeasureSpec.

MeasureSpec empaqueta SpecMode (modo) y SpecSize (tamaño específico) en un int (es decir, widthMeasureSpec, heightMeasureSpec), los primeros 2 dígitos representan SpecMode y los últimos 30 dígitos representan el tamaño.

Hay tres tipos de SpecMode

  • SIN ESPECIFICAR: el contenedor principal no tiene límite en el tamaño de la vista. Generalmente, usamos menos vistas personalizadas

  • EXACTAMENTE: configure el tamaño específico en dp en el archivo xml, o configure SpecMode en EXACTAMENTE si está configurado en match_parent.Este modo significa que SpecSize se usa directamente como el tamaño de la vista personalizada

  • AT_MOST: corresponde a wrap_content en el archivo xml, lo que indica que el tamaño establecido no puede exceder SpecSize

Por lo general, solo necesitamos reescribir onMeasure cuando necesitamos que la vista admita wrap_content.Si no se reescribe, el efecto de wrap_content es el mismo que el de match_parent. Generalmente, el siguiente código es suficiente

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        // dp转换成px, 事先定义的默认宽高单位为dp
        int defaultWidth = dp2px(getContext(), DEFAULT_WIDTH);
        int defaultHeight = dp2px(getContext(), DEFAULT_HEIGHT);

        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            widthSize = Math.min(defaultWidth, widthSize);
            heightSize = Math.min(defaultHeight, heightSize);
        } else if (widthMode == MeasureSpec.AT_MOST) {
            widthSize = Math.min(defaultWidth, widthSize);
        } else if (heightMode == MeasureSpec.AT_MOST){
            heightSize = Math.min(defaultHeight, heightSize);
        }
        
        setMeasuredDimension(widthSize, heightSize);
    }

En general, para adaptarse a diferentes pantallas, debe usar dp como unidad, y setMeasuredDimension usa px como unidad, por lo que el código anterior convierte el ancho y la altura predeterminados que configuramos en px, y el método de conversión es el siguiente

public static int dp2px(Context context, float dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dp, context.getResources().getDisplayMetrics());
    }

método onLayout

En general, el ViewGroup personalizado debe reescribir este método para descargar subvistas.

método onDraw

A través del método onDraw, podemos dibujar la vista en la pantalla. Si desea que la vista admita el atributo de relleno, debe hacerlo en onDraw

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /* mPaint为成员变量,画笔Paint的对象,
         * 在构造函数中进行初始化,可以设置一些在canvas上绘制的通用属性
         */
        mPaint.setColor(Color.RED);
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();
        //在View所占区域绘制一条对角线
        canvas.drawLine(paddingLeft, paddingTop,
                getWidth() - paddingRight, getHeight() - paddingBottom, mPaint);
    }

Precauciones

  • Los tres métodos onMeasure, onLayout y onDraw pueden llamar a esto, especialmente el método onDraw, por lo que es mejor no crear objetos en estos métodos para evitar la fluctuación de la memoria causada por la asignación frecuente de memoria, o para realizar algunas operaciones que consumen mucho tiempo y provocan errores de marco. salto a la comba

  • Hay métodos de series de publicaciones en View, por lo que no es necesario usar Handler en View

  • Si hay un hilo de creación o una animación en View que debe detenerse a tiempo, View#onDetachedFromWindow es un buen momento

  • Si hay un anidamiento deslizante en la Vista, el conflicto deslizante debe manejarse adecuadamente

referencia

 

Supongo que te gusta

Origin blog.csdn.net/u013773608/article/details/130337550
Recomendado
Clasificación