Android自定义View简介及入门

简单说说Android自定义view学习推荐的方式 这几天比较受关注,挺开心的,嘿嘿。 这里给大家总结一下学习自定义view的一些技巧。 1.首先 去看看鸿洋神的博客 Android 自定义view(1). 鸿洋神的入门教程讲的很详细。再次膜拜。 然后我这里对上面博客补充一些内容,那就是MeasureSpec相关的一些东西,这些东西我在《圆形百分比》这篇博客里提到过。 ---------------------------- 阅读详情

目录

写在前面

一、自定义View简介

1.1、什么是自定义View?

1.2、构造函数调用场景

1.3、onMeasure()方法

问题延伸(面试题):ScrollView嵌套ListView为什么会显示不全(只显示一条)?

1.4、onDraw()方法

1.5、onTouchEvent()方法

1.6、自定义属性

二、自定义TextView

2.1、自定义属性

2.2、实现TextView

2.3、在布局中使用

2.4、运行效果


写在前面

今天要说的内容可能对我来说是从做Android以来一直都是迷迷糊糊的一个东西——自定义View,这个标题很大哈,因为它涉及到的东西确实很多,这对安卓的初中级工程师来说可能也确实是一个比较晦涩难懂的东西,比如你们公司的UI设计了一个比较复杂的效果让你去做,可能你就要开始百度或者Google了,我之前也一直是这样的一个状态,因为也是工作了几年了,发现自己也是到了一个瓶颈期,所以这个时候确实需要静下来仔细思考一下,究竟该如何提升自己了,当你静下心来的时候,你会发现其实这一切都会很容易想的通,因为是进阶,所以首先你要确定好一个方向,从技术的角度来说就是先确定好一个细分领域,然后从基础开始稳扎稳打,一步一步的顺着一个技术栈由上到下做好每一个层级上的内容整合,慢慢的给自身形成一个技术体系,这样一个系列一个系列的逐个攻破,说了这么多的废话只是自己的一个思考,想表达的思想很简单就是学会思考学会总结提升自己。

一、自定义View简介

OK,废话不多说了,开始今天的内容。接下来我会分两部分来说今天的内容,第一部分对自定义View做一个介绍,第二部分通过自定义一个系统的TextView作一个入门。

1.1、什么是自定义View?

关于自定义View的概念,说实话很难用概念性的语言对它作一个定义,我在网上找了很久也没看到能说的很明白的,所以这里只能用大白话来介绍,自定义View可以分为两类,一类是继承自系统的View,另一类是继承自系统的ViewGroup,ViewGroup底层还是继承自View,并且这两类都是系统没有提供给我们的效果(系统提供的比如TextView/ImageView/Button等等)。

1.2、构造函数调用场景

当我们开始自定义View的时候,一般都会重写它的构造方法,那我们知道这些构造方法都是在什么场景下被调用的吗?

package com.jarchie.customview;

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

import androidx.annotation.Nullable;

/**
 * 作者: 乔布奇
 * 日期: 2020-04-19 16:11
 * 邮箱: jarchie520@gmail.com
 * 描述: 自定义TextView
 */
public class TextView extends View {
    public TextView(Context context) {
        super(context);
    }

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

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

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

第一个构造方法:它会在代码里面new的时候调用,比如你在代码中这样写的时候:TextView tv = new TextView(this);

第二个构造方法:在布局layout中使用的时候,比如我们在布局中这样写:

<com.jarchie.customview.TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

第三个构造方法:在布局layout中使用但是会有style的时候调用,这个是什么意思呢?比如我们布局中某个控件有很多公共的属性,一般情况下我们会将这些公共属性提取出来放在style中,然后在布局中引用style,比如:

<style name="CusTextView">
    <item name="android:layout_width">wrap_content</item>
    <item name="android:layout_height">wrap_content</item>
</style>
<!--在布局中引用我们的style-->
<com.jarchie.customview.TextView
        style="@style/CusTextView"/>

第四个构造方法:关于这个构造方法,直接拿过来你会发现它会报错,这里先不说了,等后面用到的时候再说,防止把大家搞晕了

1.3、onMeasure()方法

说到自定义View首先要说的肯定是这个函数onMeasure(),而且绝大多数情况都是离不开它的,这个函数是用来测量布局的宽高的,意思就是自定义View中布局的宽高都是由这个方法去指定的,那么它是如何去测量如何指定的呢?

我们首先来看一下上面这张图,比如蓝色部分是我们的ViewGroup,或者换句话说是父控件,内部垂直摆放两个文本控件,每个文本控件的内容填充的不一样,上面的文本宽度设置的是wrap_content,下面的文本设置的是100dp,思考一下系统是如何对各自指定对应的宽高的呢?这就要引出接下来要介绍的内容了:测量模式。

什么是测量模式呢?如何获取测量模式呢?来看下面的代码:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //获取宽高的模式
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    //获取宽高的值
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
}

通过上面的代码,我们可以清楚的看到宽高的测量模式是通过MeasureSpec.getMode()这个方法获取的,这个没啥难度,就是一个简单的api调用,OK,现在关键的是这个MeasureSpec是个啥玩意啊?不懂啊翻译一下,先从字面上看看啥意思:

谷歌给出的结果是测量规格,或者是测量说明书,不管你咋翻译,这玩意看起来都好像或多或少的决定了View的测量过程。不明觉厉,来继续往里翻,点进去看源码去,这里我简单抽取了一部分方便大家理解的:

public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        /**
         * Measure specification mode: The parent has not imposed any constraint
         * on the child. It can be whatever size it wants.
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         * Measure specification mode: The parent has determined an exact size
         * for the child. The child is going to be given those bounds regardless
         * of how big it wants to be.
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        /**
         * Creates a measure specification based on the supplied size and mode.
         * @param size the size of the measure specification
         * @param mode the mode of the measure specification
         * @return the measure specification based on size and mode
         */
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        /**
         * Extracts the mode from the supplied measure specification.
         *
         * @param measureSpec the measure specification to extract the mode from
         * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
         *         {@link android.view.View.MeasureSpec#AT_MOST} or
         *         {@link android.view.View.MeasureSpec#EXACTLY}
         */
        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

        /**
         * Extracts the size from the supplied measure specification.
         *
         * @param measureSpec the measure specification to extract the size from
         * @return the size in pixels defined in the supplied measure specification
         */
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
}

从源码上乍一看好像还挺复杂的,大体扫一遍有与或运算,还有移位运算,仔细一看,其实实现还是很简单的,来详细说说。

MeasureSpec它是一个32位的int值,高2位代表SpecMode(即测量模式),低30位代表SpecSize(即某种测量模式下的规格大小),它通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配,为了方便操作还提供了打包和解包的方法。SpecMode有三大类,每一类都有自己特殊的含义,如下所示:

  • AT_MOST:它是在布局中指定了wrap_content,也叫最大值模式,View的大小不能大于这个值
  • EXACTLY:它是在布局中指定了确切的值(100dp)或者match_parent、fill_parent,也叫精确值模式,View的最终大小就是SpecSize所指定的值
  • UNSPECIFIED:这种模式说实话用的比较少,一般是用于系统内部系统控件,它是父容器不对View有任何限制,尽可能的大,要多大给多大(ListView、ScrollView等在测量子布局的时候会用UNSPECIFIED)

问题延伸(面试题):ScrollView嵌套ListView为什么会显示不全(只显示一条)?

为什么会出现这种情况呢,其实就是ScrollView在测量子布局的时候会使用UNSPECIFIED这种测量模式,我们到源码中看一下:

首先进入源码中,查看onMeasure()方法,这里只把需要的代码展示出来:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    ...
    ...//此处省略一堆代码
}

发现它调用了父类的onMeasure()方法,我们从super.onMeasure()点击去,跟到这个方法里面看看:

看到了这个measureChild...()方法,点进去这是ViewGroup中的方法:

protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

然后回到子类ScrollView中搜索这个方法,发现该方法在子类中有自己的实现:

@Override
    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +
                heightUsed;
        final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
                Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
                MeasureSpec.UNSPECIFIED);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

childHeightMeasureSpec这个值它的处理方法中传入的是MeasureSpec.UNSPECIFIED。

然后继续找到ListView中的onMeasure()方法:

可以发现它的高度的测量模式是通过ScrollView传递进来的heightMeasureSpec,而这个值正是MeasureSpec.UNSPECIFIED,所以它的高度值最终会走到这个if语句中去:

if (heightMode == MeasureSpec.UNSPECIFIED) {
    heightSize = mListPadding.top + mListPadding.bottom + childHeight +
        getVerticalFadingEdgeLength() * 2;
}

到这里问题就找到了,这个高度值给的是什么啊?list的top+bottom+一个child的高度,所以就造成了大家遇到的那个问题,滑动的时候界面上始终只展示了一个Item。

问题找到了该如何解决呢?还是看源码,既然进到这个if语句里面有问题,那么我们不让它进到这个if里面不就OK了吗,我们让它进到下面的MeasureSpec.AT_MOST这个if里面去,measureHeightOfChildren内部具体的测量方法这里就不再继续说了,它内部其实就是会不断的测量每个Item并累加最终返回所有Item的高度了,大家有兴趣的自己对着源码看一下吧:

if (heightMode == MeasureSpec.AT_MOST) {
            // TODO: after first layout we should maybe start at the first visible position, not 0
            heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
        }

其实网上关于这个问题的解决办法还是很多的,大部分人采用的也都是上面这种解决办法,代码量比较少,很方便快捷,我们重写一个MyListView继承自ListView,重写onMeasure()方法,将heightMeasureSpec强行指定为AT_MOST模式:

package com.jk51.clouddoc.ui.widget;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ListView;

/**
 * 作者:created by Jarchie
 * 时间:2020/4/20 15:43:48
 * 邮箱:jarchie520@gmail.com
 * 说明:重写ListView,解决嵌套显示不全问题
 */
public class MyListView extends ListView {
    public MyListView(Context context) {
        super(context);
    }

    public MyListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //解决显示不全的问题 heightMeasureSpec是一个32位的值,右移两位
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

1.4、onDraw()方法

onDraw()方法在自定义View中就是用来绘制的,它可以绘制的东西有很多,文本、矩形、圆形、圆弧等等,这个方法里面全都是一些api的调用,没什么太多需要介绍的,后面使用到的时候再具体介绍吧:

//绘制的方法
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText(); //画文本
        canvas.drawArc(); //画弧
        canvas.drawCircle(); //画圆
    }

可以先看一下我之前写的一篇Android自定义绘制基础:https://blog.youkuaiyun.com/JArchie520/article/details/78199580

1.5、onTouchEvent()方法

onTouchEvent()方法主要是处理跟用户交互的相关操作,比如:手指触摸按下、移动、抬起等等,这一块涉及到的内容很多信息量很大,包括安卓面试中一个非常著名的问题:安卓的事件分发机制,今天先不分析这一块的实现,后面的话会带着源码分析一下,源码这东西是一看一脸懵逼,进去了就别想出来了,所以对于事件分发事件拦截到后面再看吧,得一步一步来,今天只了解一下最简单的几个api即可:

    /**
     * 处理跟用户交互的,手指触摸按下、移动、抬起等等
     *
     * @param event 事件分发事件拦截
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.e("TAG", "手指按下");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e("TAG", "手指移动");
                break;
            case MotionEvent.ACTION_UP:
                Log.e("TAG", "手指抬起");
                break;
        }
        return super.onTouchEvent(event);
    }

1.6、自定义属性

自定义属性就是用来配置的,比如:android:text="Jarchie"是系统的一个自定义属性,当我们在自己写自定义View的时候还这样写可以吗?那当然不可以了,所以我们需要自己去定义你的View的相关属性,那么该如何做呢?

首先我们需要在res/values文件夹下新建一个属性的配置文件,名字可以随意起,但是为了规范,一般命名为attrs.xml,然后xml内部如何编写呢?其实写法都是按套路来就好了,具体的如何定义注释我都放在代码里了,举个栗子:

<resources>
    <!--name 自定义View的名字 TextView-->
    <declare-styleable name="TextView">
    <!-- name 属性名称
    format 格式: string 文字  color 颜色
                dimension 宽高 字体大小
                integer 数字 比如最大长度
                reference 资源(drawable)
    -->
        <attr name="text" format="string" />
        <attr name="textColor" format="color" />
        <attr name="textSize" format="dimension" />
        <attr name="maxLength" format="integer" />
        <attr name="background" format="reference|color" />

        <!-- 枚举 -->
        <attr name="inputType">
            <enum name="number" value="1" />
            <enum name="text" value="2" />
            <enum name="password" value="3" />
        </attr>

    </declare-styleable>
</resources>

然后定义了相关属性之后,如何在布局中使用呢?

我们需要声明命名空间:xmlns:jarchie="http://schemas.android.com/apk/res-auto",然后在自己的自定义View中使用,注意命名空间的名称和下方引用的名称需保持一致,名称可以随意起,但是一般都定义为app,我这里用自己的名字做个演示:

最后是如何在自定义View中获取配置的属性呢?其实也都是按套路来的代码了,一起来看一下:

然后你一运行即将发现意外之喜,哎呦报错啦,没错不能这样写哈,因为上面我们在attrs.xml里面定义的那些属性,系统都是有的,也就是说系统不允许你将它已经有的属性重新定义,所以给你一顿报错,该如何解决呢?也很简单啊,属性名称全部改掉,改成系统没有的然后就OK了,比如上面的:<attr name="text" format="string" />这个name就不能这样写了,要改成系统没有的<attr name="mytext" format="string" />,一定要记住哈!到这里,自定义View的简介就先说这么多,第二部分会结合上面讲的内容来完成自定义TextView的编写。

二、自定义TextView

这一部分通过上面介绍的基础知识,来写一个入门的自定义View的案例:自定义TextView。

2.1、自定义属性

    <!--自定义TextView-->
    <declare-styleable name="TextView">
        <attr name="archieText" format="string"/>
        <attr name="archieTextColor" format="color"/>
        <attr name="archieTextSize" format="dimension"/>
        <attr name="archieMaxLength" format="integer"/>
        <!--background 自定义View都是继承自View,背景都是由View管理的-->
        <!--<attr name="archieBackground" format="reference"/>-->

        <attr name="archieInputType">
            <enum name="number" value="1"/>
            <enum name="text" value="2"/>
            <enum name="password" value="3"/>
        </attr>
    </declare-styleable>

2.2、实现TextView

package com.healthrm.ningxia.ui.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

import com.healthrm.ningxia.R;

/**
 * 作者:created by Jarchie
 * 时间:2020/4/21 09:15:29
 * 邮箱:jarchie520@gmail.com
 * 说明:安卓自定义TextView
 */
public class TextView extends View {
    private String mText;
    private int mTextSize = 15;
    private int mTextColor = Color.BLACK;
    private Paint mPaint;

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

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

    public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //获取自定义属性
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TextView);
        mText = array.getString(R.styleable.TextView_archieText);
        mTextColor = array.getColor(R.styleable.TextView_archieTextColor, mTextColor);
        mTextSize = array.getDimensionPixelSize(R.styleable.TextView_archieTextSize, sp2px(mTextSize));
        array.recycle();
        mPaint = new Paint();
        mPaint.setAntiAlias(true); //抗锯齿
        mPaint.setTextSize(mTextSize); //画笔大小
        mPaint.setColor(mTextColor); //画笔颜色
    }

    //sp转px
    private int sp2px(int sp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取宽高的测量模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //1、确定的值,这种情况不需要计算,给的多少就是多少
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        //2、wrap_content,需要计算
        if (widthMode == MeasureSpec.AT_MOST) {
            //计算的宽度与字体的长度、大小有关,用画笔来
            Rect bounds = new Rect();
            //获取文本的rect
            mPaint.getTextBounds(mText, 0, mText.length(), bounds);
            width = bounds.width() + getPaddingLeft() + getPaddingRight();
        }
        if (heightMode == MeasureSpec.AT_MOST) {
            Rect bounds = new Rect();
            mPaint.getTextBounds(mText, 0, mText.length(), bounds);
            height = bounds.height() + getPaddingTop() + getPaddingBottom();
        }
        //设置控件的宽高
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //画文字 text   x:开始位置   y:基线    paint
        //dy代表的是高度的一半到baseLine的距离
        Paint.FontMetricsInt fontMetricsInt = mPaint.getFontMetricsInt();
        //top是一个负值 bottom是一个正值 top、bottom的值代表的是baseLine到文字顶部和底部的距离
        int dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
        int baseLine = getHeight() / 2 + dy;
        int x = getPaddingLeft();
        canvas.drawText(mText, x, baseLine, mPaint);
    }
}

2.3、在布局中使用

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.healthrm.ningxia.ui.view.TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:archieText="Jarchie"
        android:padding="10dp"
        app:archieTextColor="@color/colorAccent"/>
    
    <com.healthrm.ningxia.ui.view.TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:archieText="Jarchie's Blog"
        android:padding="10dp"
        android:background="@color/colorAccent"
        app:archieTextColor="@color/red"/>
</LinearLayout>

2.4、运行效果

这就是我们的一个入门级的自定义View,今天的内容就这么多了,下期再会!

安卓面试之自定义View流程及原理 自定义View原理学习博客整理 阅读详情

相关推荐

Android自定义View入门教程

Android自定义View入门教程 写在前面 如果你一直在苦恼产品经理总是抱怨“为什么苹果可以做,Android就不可以balabala…",如果你希望理直气壮的对设计狮来一句”不要问我能不能做,我只想知道你想不想要…" Android自定义View是每一个Android开发者成为高级开发工程师的必经之路,今天我们就来以继承View的方式实现一个简单的加载进度条,讲解Android自定义View的步骤。 奉上产送门:https://github.com/zhijunhong/custom_view/tre

ZhijunHong的博客 569

android中view组件使用详解

public class View extends Object implements Drawable.Callback KeyEvent.Callback AccessibilityEventSource java.lang.Object

sunxingzhesunjinbiao的专栏 3700

Android自定义View开发:从入门到精通

目的在于帮助开发者系统地学习Android自定义View开发,从最基础的概念开始,逐步深入到核心算法、数学模型以及实际项目开发。范围覆盖了自定义View的绘制、事件处理、动画效果等多个方面,旨在让读者能够独立开发出高质量的自定义View组件。本文将按照以下结构进行阐述:首先介绍核心概念与联系,让读者对自定义View有一个整体的认识;接着详细讲解核心算法原理和具体操作步骤,结合Python代码示例进行说明;然后介绍数学模型和公式,并通过举例加深理解;之后进行项目实战,包括开发环境搭建、源代码实现和解读;

移动开发前沿的博客 1109

android View基本知识

本文参考了博客 https://www.zybuluo.com/TryLoveCatch/note/722664 View 获取坐标方法 getTop(): 获取View顶部相对父布局的距离 getLeft(): 获取View左侧相对父布局的距离 getBottom():获取View底部相对父布局的距离 getRight(): 获取View右侧相对父布局的距离 getTranslationX(

kecongxiao的博客 562

Selenium实战:突破反爬,自动化抓取国家知识产权局专利数据

网络爬虫是数据采集的核心技术,其原理是通过程序模拟浏览器行为,自动访问网页并提取结构化信息。在数据驱动决策的今天,高效获取公开数据对市场分析、技术调研和竞争情报至关重要。然而,面对广泛应用的JavaScript动态渲染和反爬虫机制,传统的静态页面抓取方法往往失效。Selenium作为浏览器自动化工具,通过驱动真实浏览器执行JavaScript,能够有效绕过前端反爬,实现“可见即可得”的数据抓取,在需要与页面进行复杂交互或处理动态内容的场景中价值显著。本文以【专利数据】抓取为例,结合【Selenium】工具,

weixin_33881140的博客 394

【笔记软件obsidian】从入门到精通

目录 一、前言 二、下载及安装 三、【选修】ios用户如何通过iCloud进行三平台同步 四、基本Markdown语法 五、【选修】插件的下载及安装方式 六、

patrick的博客 9万+

在 Android 中使用各种控件(View)

TextView -文本显示控件  Button -按钮控件  ImageButton -图片按钮控件  ImageView -图片显示控件  CheckBox -复选框控件  RadioButton -单选框控件  AnalogClock -钟表(带表盘的那种)控件  DigitalClock -电子表控件   1、TextView的 Demo  textview.xml 代

c13_tianming的专栏 680

在unity urp项目中 通过图片创建材质(透明光晕)

unity中通过图片创造材质(urp管道)

m0_49721415的博客 1541

android常见错误一

今天用xUtils框架做开发时,遇到一个奇怪的错误。如下所示: 03-29 21:46:13.014: E/SubmitMessage(20680): Submit string: submit:trigger=0,bugtype=2,modulename=com.wangchao.news,level=1,testtype=NORMAL,path=/data/log/unzip/H60-L02

風林火山的博客 5061

美亚杯2023参赛全攻略:从检材下载到Veracrypt挂载的保姆级教程

本文提供美亚杯2023电子数据取证竞赛的完整参赛指南,重点解析从检材下载、Veracrypt挂载到实战取证的保姆级流程。针对新手常见误区,详细说明两套密码(ZIP解压密码与Veracrypt挂载密码)的区别与正确使用方法,并分享赛前环境搭建、赛中高效分析及赛后报告撰写的核心技巧,帮助参赛者系统备赛,规避基础错误。

y9z0a1b的博客 1079

Hi3516CV610 YOLOv8模型转换与部署实战:从源码到板端推理全流程解析

本文详细解析了将YOLOv8模型部署到Hi3516CV610开发板的全流程。内容涵盖从Ubuntu环境搭建、模型获取与验证,到关键的PyTorch模型转换为ONNX格式,并利用海思工具链量化生成板端可执行的.wk文件。最后,指导开发者编写板端C程序,完成视频流处理、NNIE推理及结果后处理,并提供了部署调试与性能优化的实用技巧。

weixin_29215815的博客 268

Android开发之自定义View

在自定义View的时候,需要两个构造函数。否则在编译的时候会报异常:Binary XML file line Error inflating class. 原因是:Android在根据xml文件夹创建View对象的时候会调用View的双参构造方法,即public SquareView(Context context, AttributeSetattrs),所以如果没有写全的话就会报错。...

weixin_43858011的博客 1万+

Windows系统离线搭建PyTorch深度学习环境全攻略(CUDA+cudnn实战)

本文提供了一份在Windows系统下离线搭建PyTorch深度学习环境的完整实战指南。文章详细讲解了如何在没有网络连接的情况下,正确安装和配置CUDA、cudnn以及PyTorch,并构建独立的Python虚拟环境。重点强调了版本依赖关系的重要性,并提供了从驱动准备到环境验证的每一步操作,旨在帮助用户高效、稳定地部署可复用的GPU深度学习开发环境。

table的专栏 579

Android自定义View

概述 Android开发进阶的必经之路 为什么要自定义View 自定义View的基本方法 自定义View的最基本的三个方法分别是: onMeasure()、onLayout()、onDraw(); View在Activity中显示出来,要经历测量、布局和绘制三个步骤,分别对应三个动作:measure、layout和draw。 测量:onMeasure()决定View的大小; 布局:onLayout()决定View在ViewGroup中的位置; 绘制:onDraw()决定绘制这个View。 自定义控

Rayman的博客 1万+

uniapp [移动端H5网页] - 强力解决弹起软键盘后页面被挤压变形或页面整体上移问题,点击input输入框键盘弹出底部被上推往上顶遮挡及各种问题(兼容安卓苹果ios手机,微信公众号网页也适用)

uni-app,h5网页,input输入框组件,uniapp h5软键盘弹出,uni-app 输入框,键盘弹起时页面整体上移问题,uni,H5页面,解决软键盘弹起页面被挤压、被往顶的问题,解决uni-app 输入框,键盘弹起时页面整体上移问题,uniapp调用手机系统软键盘,页面窗口被顶起,布局定位失效,解决uniapp软键盘弹起导致页面fixed定位元素被顶上去,输入法软键盘弹起,窗口顶起,变形解决办法,UniApp中input组件在IOS设备上弹出软键盘时页面整体上移问题的解决方案,以及input组件聚

🏆 前端领域优质创作者 1万+

【2026最新版】Open3D(C++)点云处理算法汇总(C++长期更新版)

C++版本Open3D进行点云处理的一些算法使用案例。博客长期更新,最近一次更新时间为:2026年4月11日。

点云侠的博客 1万+

安卓自定义View

安卓自定义View

eos-i的博客 5250

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

安卓自定义View进阶-Canvas之绘制基本形状

在上一篇自定义 View 分类与流程中我们了解自定义 View 相关的基本知识,不过,这些东西依旧还是理论,并不能拿来 (zhuang) 用(B), 这一次我们就了解一些能 (zhaung) 用(B)的东西。 在本篇文章中,我们先了解 Canvas 的基本用法,最后用一个小示例来结束本次教程。

GcsSloop 1万+

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

深入理解Android之自定义View

自定义View、多线程、网络,被认为是Android开发者必须牢固掌握的最基础的三大基本功。而自定义View又是Android开发中特别重要的一环,很多地方都需要用到自定义View。这篇文章我就梳理一下自定义View的相关知识。 目录 (一)什么是自定义View (二)自定义组合View (三)自定义View的绘制 1. Canvas.drawXXX() Pa...

Fighting_初心的博客 1万+

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

安卓自定义view(全面)

安卓自定义view 一、view的测量 1.测量的模式: EXACTLY :精确值模式,当把空间的layout_width属性或者layout_height的属性设置为具体的数值时,或者指定为match_parent时候,系统就是使用的EXACTLY 模式; AT_MOST: 最大值模式,当空间的layout_width属性或者layout_height属性指定为wrap_content时候,控件大小跟着控件的内容变化而变化,此时控件的尺寸只要不超过父控件允许的最大尺寸即可。 UNSPECIFIED: 这

twenty_four_7的博客 3254

android自定义view

在Android开发中有很多业务场景,原生的控件是无法满足应用,并且经常也会遇到一个UI在多处 重复使用情况,那么就需要通过自定义View的方式来实现这些UI效果。作为一个Android开发工程师自定义View属于一个必备技能。自定义View的实现方式有以下几种: 组合控件,继承控件,自绘控件详细可分为:自定义组合控件,继承系统View控件,继承系统ViewGroup,自绘View控件,自绘ViewGroup控件组合控件就是将多个控件组合成一个新的控件,可以重复使用。...

shuo277的博客 1万+

安卓自定义View之旋转麦克风

在项目的\res\values\attrs.xml 中 新增一个 declare-styleable 名字就叫RotateView。为了好看出是在旋转,静止和旋转状态都定义了俩种颜色。份数的简单计算方式:360度除以每份的度数再取整即可,份数 = (int) (360/ 每份扫过的角度)--静止状态下的第1种颜色-->--静止状态下的第2种颜色-->--旋转状态下的第1种颜色-->--旋转状态下的第2种颜色-->--静止状态下画笔描边宽度-->--旋转状态下画笔描边宽度-->

csdn_too的博客 1612

《Android 自定义View的详解》

自定义View:只需要重写onMeasure()和onDraw()自定义ViewGroup:只需要重写onMeasure()和onLayout():用于测量View的大小(宽度和高度)。:确定View在ViewGroup中的位置;:用于绘制View的内容。,在里面写上你的触摸反馈算法,并返回 true(关键是 ACTION_DOWN 事件时返回 true)。如果是会发生触摸冲突的 ViewGroup,还需要重写。

weijiangbc0的博客 1401

安卓View理解总结

安卓View理解总结 参考 link.网站写的一篇博客; 一.视图坐标系. View是Android所有控件的基类,同时ViewGroup也是继承自View,下面这张图可以直观了解。 1.Android坐标系. Android中有两种坐标系,分别为Android坐标系和视图坐标系,首先我们先来看看Android坐标系。 在Android中,将屏幕的左上角的顶点作为Android坐标系的原点,这个...

gzy19805026167的博客 2562

重学Android:自定义View基础(一)

作为一名安卓开发,也被称为大前端,做一个美观的界面,是我们必备的基础技能,可能在开发中我们最常用的是系统自带的View,因为他能满足绝大部分需求,难一点的我们也可以上Github上找个三方库使用,少数情况下会让我们进行自定义View,当然这不代表着我们可以不去掌握其原理,因为它是通往中高级程序员的必经之路,也是大厂面试的热门知识,只有熟练掌握其核心原理,才能让我们在后续的开发中游刃有余。

WriteBug001的博客 1293

Android自定义View-入门(明白自定义View和自定义ViewGroup)

自定义View 为什么要自定义View? 主要是Andorid系统内置的View 无法实现我们的 需求,我们需要针对我们的业务需求定制我们想要的 View.自定义View 我们大部分时候只需重写两个函数: onMeasure(),onDraw(). onMeasure()负责对当前View 的尺寸进行测量,onDraw负责把当前这个View绘制出来,当然了,还需要写构造函数。 public Vi...

petterp的博客 3615
上一篇: 快速搞定移动端混合开发基础
下一篇: 自定义View之仿QQ运动步数进度效果
江湖客陆仁甲
博客等级 码龄10年 179粉丝 98原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值