自定义View详解

Android自定义View系列之详解View的绘制流程 一、开场白 开讲之前我们先预设一种自定义ViewGroup的场景:我们知道LinearLayout、FrameLayout、RelativeLayout...都是系统定义实现的布局,我们想要自定义一个FlowLayout流式布局实现我们自己要的效果(自定义不就是按照自己想要的效果实现的一种布局),流式布局目前Google官方还没帮我们提供,但网上有很多自定义的流式布局,也都是开发者们自己继承ViewGroup实现的,今天以自定义ViewGroup实现流式布局FlowLayout为大前提讲解View的绘. 阅读详情

自定义View详解

虽然之前也分析过View回执过程,但是如果让我自己集成ViewGroup然后自己重新onMeasure,onLayout,onDraw方法自定义View我还是会头疼。今天索性来系统的学习下。

onMeasure

/**
     * <p>
     * Measure the view and its content to determine the measured width and the
     * measured height. This method is invoked by {@link #measure(int, int)} and
     * should be overridden by subclasses to provide accurate and efficient
     * measurement of their contents.
     * </p>
     *
     * <p>
     * <strong>CONTRACT:</strong> When overriding this method, you
     * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
     * measured width and height of this view. Failure to do so will trigger an
     * <code>IllegalStateException</code>, thrown by
     * {@link #measure(int, int)}. Calling the superclass'
     * {@link #onMeasure(int, int)} is a valid use.
     * </p>
     *
     * <p>
     * The base class implementation of measure defaults to the background size,
     * unless a larger size is allowed by the MeasureSpec. Subclasses should
     * override {@link #onMeasure(int, int)} to provide better measurements of
     * their content.
     * </p>
     *
     * <p>
     * If this method is overridden, it is the subclass's responsibility to make
     * sure the measured height and width are at least the view's minimum height
     * and width ({@link #getSuggestedMinimumHeight()} and
     * {@link #getSuggestedMinimumWidth()}).
     * </p>
     *
     * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
     *                         The requirements are encoded with
     *                         {@link android.view.View.MeasureSpec}.
     * @param heightMeasureSpec vertical space requirements as imposed by the parent.
     *                         The requirements are encoded with
     *                         {@link android.view.View.MeasureSpec}.
     *
     * @see #getMeasuredWidth()
     * @see #getMeasuredHeight()
     * @see #setMeasuredDimension(int, int)
     * @see #getSuggestedMinimumHeight()
     * @see #getSuggestedMinimumWidth()
     * @see android.view.View.MeasureSpec#getMode(int)
     * @see android.view.View.MeasureSpec#getSize(int)
     */
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

注释说的非常清楚。但是我还是要强调一下这两个参数:widthMeasureSpec和heightMeasureSpec这两个int类型的参数,看名字应该知道是跟宽和高有关系,但它们其实不是宽和高,而是由宽、高和各自方向上对应的模式来合成的一个值:其中,在int类型的32位二进制位中,31-30这两位表示模式,0~29这三十位表示宽和高的实际值.其中模式一共有三种,被定义在Android中的View类的一个内部类中:View.MeasureSpec:

android.view
public static class View.MeasureSpec
extends Object
A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode. There are three possible modes:
UNSPECIFIED
The parent has not imposed any constraint on the child. It can be whatever size it wants.
EXACTLY
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.
AT_MOST
The child can be as large as it wants up to the specified size.
MeasureSpecs are implemented as ints to reduce object allocation. This class is provided to pack and unpack the <size, mode> tuple into the int.
  • MeasureSpec.UNSPECIFIED The parent has not imposed any constraint on the child. It can be whatever size it wants. 这种情况比较少,一般用不到。标示父控件没有给子View任何显示- - - -对应的二进制表示: 00
  • MeasureSpec.EXACTLY 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.
    理解成MATCH_PARENT或者在布局中指定了宽高值,如layout:width=’50dp’. - - - - 对应的二进制表示:01
  • MeasureSpec.AT_MOST The child can be as large as it wants up to the specified size.理解成WRAP_CONTENT,这是的值是父View可以允许的最大的值,只要不超过这个值都可以。- - - - 对应的二进制表示:10

那具体MeasureSpec是怎么把宽和高的实际值以及模式组合起来变成一个int类型的值呢? 这部分是在MeasureSpce.makeMeasureSpec()方法中处理的:

public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

那我们如何从MeasureSpec值中提取模式和大小呢?该方法内部是采用位移计算.

/**
 * 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}
 */
public static int getMode(int measureSpec) {
    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);
}

onLayout

为了能合理的去绘制定义View,你需要制定它的大小。复杂的自定义View通常需要根据屏幕的样式和大小来进行复杂的布局计算。你不应该假设你的屏幕上的View的大小。即使只有一个应用使用你的自定义View,也需要处理不同的屏幕尺寸、屏幕密度和横屏以及竖屏下的多种比率等。

虽然View有很多处理测量的方法,但他们中的大部分都不需要被重写。如果你的View不需要特别的控制它的大小,你只需要重写一个方法:onSizeChanged()。

onSizeChanged()方法会在你的View第一次指定大小后调用,在因某些原因改变大小后会再次调用。在上面PieChart的例子中,onSizeChanged()方法就是它需要重新计算表格样式和大小以及其他元素的地方。
下面就是PieChart.onSizeChanged()方法的内容:

// Account for padding
float xpad = (float)(getPaddingLeft() + getPaddingRight());
float ypad = (float)(getPaddingTop() + getPaddingBottom());

// Account for the label
if (mShowText) xpad += mTextWidth;

float ww = (float)w - xpad;
float hh = (float)h - ypad;

// Figure out how big we can make the pie.
float diameter = Math.min(ww, hh);

onDraw

自定义View最重要的就是展现样式。

重写onDraw()方法

绘制自定义View最重要的步骤就是重写onDraw()方法。onDraw()方法的参数是Canvas对象。可以用它来绘制自身。Canvas类定义了绘制文字、线、位图和很多其他图形的方法。你可以在onDraw()方法中使用这些方法来指定UI.

在使用任何绘制方法之前,你都必须要创建一个Paint对象。

创建绘制的对象

android.graphics框架将绘制分为两步:

  • 绘制什么,由Canvas处理。
  • 怎么去绘制,由Paint处理。
Canvas

The Canvas class holds the “draw” calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels,
a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap),
and a paint (to describe the colors and styles for the drawing).

  • Canvas():创建一个空的画布,可以使用setBitmap()方法来设置绘制的具体画布;
  • Canvas(Bitmap bitmap):以bitmap对象创建一个画布,则将内容都绘制在bitmap上,bitmap不得为null;
  • canvas.drawRect(RectF,Paint)方法用于画矩形,第一个参数为图形显示区域,第二个参数为画笔,设置好图形显示区域Rect和画笔Paint后,即可画图;
  • canvas.drawRoundRect(RectF, float, float, Paint)方法用于画圆角矩形,第一个参数为图形显示区域,第二个参数和第三个参数分别是水平圆角半径和垂直圆角半径。
  • canvas.drawLine(startX, startY, stopX, stopY, paint):前四个参数的类型均为float,最后一个参数类型为Paint。表示用画笔paint从点(startX,startY)到点(stopX,stopY)画一条直线;
  • canvas.drawLines (float[] pts, Paint paint)``pts:是点的集合,大家下面可以看到,这里不是形成连接线,而是每两个点形成一条直线,pts的组织方式为{x1,y1,x2,y2,x3,y3,……},例如float []pts={10,10,100,100,200,200,400,400};就是有四个点:(10,10)、(100,100),(200,200),(400,400)),两两连成一条直线;
  • canvas.drawArc(oval, startAngle, sweepAngle, useCenter, paint):第一个参数oval为RectF类型,即圆弧显示区域,startAngle和sweepAngle均为float类型,分别表示圆弧起始角度和圆弧度数,3点钟方向为0度,useCenter设置是否显示圆心,boolean类型,paint为画笔;
  • canvas.drawCircle(float,float, float, Paint)方法用于画圆,前两个参数代表圆心坐标,第三个参数为圆半径,第四个参数是画笔;
  • canvas.drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) 位图,参数一就是我们常规的Bitmap对象,参数二是源区域(这里是bitmap),参数三是目标区域(应该在canvas的位置和大小),参数四是Paint画刷对象,因为用到了缩放和拉伸的可能,当原始Rect不等于目标Rect时性能将会有大幅损失。
  • canvas.drawText(String text, float x, floaty, Paint paint)渲染文本,Canvas类除了上
    面的还可以描绘文字,参数一是String类型的文本,参数二x轴,参数三y轴,参数四是Paint对象。
  • canvas.drawPath (Path path, Paint paint),根据Path去画.
    java
    Path path = new Path();
    path.moveTo(10, 10); //设定起始点
    path.lineTo(10, 100);//第一条直线的终点,也是第二条直线的起点
    path.lineTo(300, 100);//画第二条直线
    path.lineTo(500, 100);//第三条直线
    path.close();//闭环
    canvas.drawPath(path, paint);
Paint
  • setARGB(int a, int r, int g, int b) 设置Paint对象颜色,参数一为alpha透明值
  • setAlpha(int a) 设置alpha不透明度,范围为0~255
  • setAntiAlias(boolean aa)是否抗锯齿
  • setColor(int color)设置颜色
  • setTextScaleX(float scaleX)设置文本缩放倍数,1.0f为原始
  • setTextSize(float textSize)设置字体大小
  • setUnderlineText(String underlineText)设置下划线

例如,Canvas提供了一个画一条线的方法,而Paint提供了指定这条线的颜色的方法。Canvas提供了绘制长方形的方法,而Paint提供了是用颜色填充整个长方形还是空着的方法。简单的说,Canvas指定了你想在屏幕上绘制的形状,而Paint指定了你要绘制的形状的颜色、样式、字体和样式等等。

所以,在你draw任何东西之前,你都需要创建一个或者多个Paint对象。下面的PieChart例子就是在构造函数中调用的init方法:

private void init() {
   mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
   mTextPaint.setColor(mTextColor);
   if (mTextHeight == 0) {
       mTextHeight = mTextPaint.getTextSize();
   } else {
       mTextPaint.setTextSize(mTextHeight);
   }

   mPiePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
   mPiePaint.setStyle(Paint.Style.FILL);
   mPiePaint.setTextSize(mTextHeight);

   mShadowPaint = new Paint(0);
   mShadowPaint.setColor(0xff101010);
   mShadowPaint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL));

   ...

下面是PieChart完整的onDraw()方法:

protected void onDraw(Canvas canvas) {
   super.onDraw(canvas);

   // Draw the shadow
   canvas.drawOval(
           mShadowBounds,
           mShadowPaint
   );

   // Draw the label text
   canvas.drawText(mData.get(mCurrentItem).mLabel, mTextX, mTextY, mTextPaint);

   // Draw the pie slices
   for (int i = 0; i < mData.size(); ++i) {
       Item it = mData.get(i);
       mPiePaint.setShader(it.mShader);
       canvas.drawArc(mBounds,
               360 - it.mEndAngle,
               it.mEndAngle - it.mStartAngle,
               true, mPiePaint);
   }

   // Draw the pointer
   canvas.drawLine(mTextX, mPointerY, mPointerX, mPointerY, mTextPaint);
   canvas.drawCircle(mPointerX, mPointerY, mPointerSize, mTextPaint);
}

下面是一张View绘制过程中框架调用的一些标准方法概要图:
image

下面来几个例子:

自定义开关:

public class ToogleView extends View {
    private int mSlideMarginLeft = 0;
    private Bitmap backgroundBitmap;
    private Bitmap slideButton;


    public ToogleView(Context context) {
        super(context);
        init(context);
    }

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

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

    private void init(Context context) {
        backgroundBitmap = BitmapFactory.decodeResource(getResources(),
                R.drawable.toogle_bg);
        slideButton = BitmapFactory.decodeResource(getResources(),
                R.drawable.toogle_slide);
        this.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mSlideMarginLeft == 0) {
                    mSlideMarginLeft = backgroundBitmap.getWidth() - slideButton.getWidth();
                } else {
                    mSlideMarginLeft = 0;
                }
                invalidate();
            }
        });
    }

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

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint paint = new Paint();
        paint.setAntiAlias(true);
    // 先画背景图
        canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
    // 再画滑块,用mSlideMarginLeft来控制滑块距离左边的距离。
        canvas.drawBitmap(slideButton, mSlideMarginLeft, 0, paint);
    }
<LinearLayout android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    xmlns:android="http://schemas.android.com/apk/res/android" >

<com.charon.recyclerviewdemo.ToogleView
        android:paddingLeft="50dp"
        android:background="@android:color/holo_green_light"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    </LinearLayout>

image
很明显显示的不对,因为高设置为warp_content了,但是界面显示的确实整个屏幕,而且paddingLeft也没生效,那该怎么做呢? 当然是重写onMeasure() 方法:

public class ToogleView extends View {
    private int mSlideMarginLeft = 0;
    private Bitmap backgroundBitmap;
    private Bitmap slideButton;


    public ToogleView(Context context) {
        super(context);
        init(context);
    }

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

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

    private void init(Context context) {
        backgroundBitmap = BitmapFactory.decodeResource(getResources(),
                R.drawable.toogle_bg);
        slideButton = BitmapFactory.decodeResource(getResources(),
                R.drawable.toogle_slide);
        this.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mSlideMarginLeft == 0) {
                    mSlideMarginLeft = backgroundBitmap.getWidth() - slideButton.getWidth();
                } else {
                    mSlideMarginLeft = 0;
                }
                invalidate();
            }
        });
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
        int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);

        int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
        int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
        int width;
        int height;
        if (MeasureSpec.EXACTLY == measureWidthMode) {
            width = measureWidth;
        } else {
            width = backgroundBitmap.getWidth();
        }

        if (MeasureSpec.EXACTLY == measureHeightMode) {
            height = measureHeight;
        } else {
            height = backgroundBitmap.getHeight();
        }

        setMeasuredDimension(width, height);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        canvas.drawBitmap(backgroundBitmap, getPaddingLeft(), 0, paint);
        canvas.drawBitmap(slideButton, mSlideMarginLeft + getPaddingLeft(), 0, paint);
    }

}

这样就可以了。简单的说明一下,就是如果当前的模式是EXACTLY那就把父View传递进来的宽高设置进来,如果是AT_MOST或者UNSPECIFIED的话就使用背景图片的宽高。

最后再来一个自定义ViewGroup的例子:

之前的引导页面都是通过类似ViewPager这种方法左右滑动,现在想让他上下滑动,该怎么弄呢?

public class VerticalLayout extends ViewGroup {
    public VerticalLayout(Context context) {
        super(context);
    }
    public VerticalLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

    }
}

继承ViewGroup必须要重写onLayout方法。其实这也很好理解,因为每个ViewGroup的排列方式不一样,所以让子类来自己实现是最好的。
当然畜类重写onLayout之外,也要重写onMeasure。
代码如下,滑动手势处理的部分就不贴了。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int measureSpec = MeasureSpec.makeMeasureSpec(mScreenHeight
                * getChildCount(), MeasureSpec.getMode(heightMeasureSpec));
        super.onMeasure(widthMeasureSpec, measureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 就像猴子捞月一样,让他们一个个的从上往下排就好了
        if (changed) {
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                if (child.getVisibility() != View.GONE) {
                    child.layout(l, i * mScreenHeight, r, (i + 1)
                            * mScreenHeight);
                }
            }
        }
    }

上面介绍了通过继承View以及ViewGroup的方式来自定义View,平时开发过程中有时不需要继承他俩,我们直接继承功能接近
的类进行扩展就好,例如:我想自定义一个Meterial Design样式的EditText。那我们该怎么实现呢? 当然是继承EditText了,它比EditText多了一条底下的线,那我们给它draw上就可以了。

image

public class MetrailEditText extends EditText {
    private NinePatchDrawable mDrawable;

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

    public MetrailEditText(Context context) {
        super(context);
        init();
    }

    private void init() {
        setBackgroundResource(0);
        mDrawable = (NinePatchDrawable) getResources().getDrawable(R.drawable.edittext_meterial_bg_activated);
    }

    @Override
    protected void onDraw(final Canvas canvas) {
        super.onDraw(canvas);
        mDrawable.setBounds(-getCompoundPaddingLeft(), 0, getWidth() + getCompoundPaddingRight(), getHeight());
        mDrawable.draw(canvas);
    }
}

看到这里你可能会糊涂,这哪行啊? 我们的edittext_meterial_bg_activated可不是普通的图,当然是9 patch图了。

image

当然你可以在onDraw()的时候加一个自定义线的颜色mDrawable.setColorFilter(mLineColor, PorterDuff.Mode.SRC_ATOP);等。

参考部分:
- http://blog.youkuaiyun.com/cyp331203/article/details/40736027


更多内容请关注AndroidNote

Android 用自定义view画圆详解 这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入 欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Mar 阅读详情

相关推荐

Android自定义视图实现圆弧和圆角技术详解

XML(Extensible Markup Language)是一种标记语言,它允许开发者定义自己的标签格式,用于存储和传输数据。在Android开发中,XML常被用于定义UI界面布局、菜单、样式和主题等。它通过声明式的语法使界面描述更加直观,并且可以轻松地被设计师和开发人员理解和修改。在Android开发中,自定义View提供了无限的灵活性来绘制用户界面。创建一个自定义View通常涉及继承一个现有的View类,如View或ViewGroup,并重写其构造函数和onDraw方法。

weixin_32661831的博客 1619

RecyclerView加上弧形滚动条

界面主要是通过GridLayoutManager设置每行显示两个item;   mRecyclerView = (MyRecyclerView) rootView.findViewById(R.id.id_recyclerview_grid);         mAppAddBtn = (ImageView)  rootView.findViewById(R.id.app_l...

菜鸟的博客 2893

别再让地图加载慢吞吞!GeoServer + GeoWebCache 缓存配置实战(附性能对比)

本文详细介绍了如何通过GeoServer和GeoWebCache优化地图服务性能,实现地图加载速度的显著提升。从缓存机制解析到实战配置流程,再到高级性能调优技巧,帮助开发者构建高效地图缓存系统,解决地图加载慢的问题。

weixin_42525369的博客 407

Android—RecyclerView进阶(5)—自定义LayoutManager

我的优快云: ListerCi 我的简书: 东方未曦 一、简介&示例 虽然官方提供的LinearLayoutManager和GridLayoutManager等已经可以满足绝大部分需求了,但是当我们对Item的布局有特殊的需求时就需要我们自定义LayoutManager。自定义LayoutManager作为RecyclerView的一大难点,对自定义View和RecyclerView复用...

ListerCi的博客 1095

深度探索:机器学习中的最大似然估计(MLE)原理及其应用

最大似然估计作为统计推断的核心方法,在机器学习领域具有广泛的应用价值和深远影响。尽管存在对模型假设敏感、易受异常值影响等局限性,但其直观的原理、坚实的理论基础以及高效的实现方式使其在实践中仍占据主导地位。随着深度学习、强化学习等新兴领域的快速发展,最大似然估计将继续演化并适应新的模型结构与优化框架。同时,结合现代优化算法、并行计算技术以及贝叶斯方法的融合,最大似然估计有望在保持其核心优势的同时,进一步提升参数估计的准确性和鲁棒性,为未来的机器学习研究与应用开辟更广阔的道路。

qq_51320133的博客 1万+

Echarts之风力风速玫瑰图

风力风速玫瑰图前言改进之路代码如下总结导出与导入 前言 今天上班收到任务,我们老大让我研究一下这个“风速风向玫瑰图”,还说这个是百度的雷达图的变异体,于是乎我直接找的雷达图,但是雷达图没有这种柱形的,都是点线,然后我又找了好久,最后突然在echarts官网上面看到了图1-2,才终于有了思路,原来这个图不是雷达图的变异体,而是极坐标系下的堆叠柱状图,于是就开始我的“改进”之路了。 图1-1 改进之路...

qq_38776605的博客 1万+

记一次自定义View: 扇形圆环

Android 完全自定义View: 渐变色扇形圆环, 附带增长动画; 用 Paint, Canvas 绘制圆环.

孟老板的博客 590

半导体设备隔振必看:如何用天棚阻尼PID控制实现VC-C级振动抑制(含SIMULINK模型)

本文深入探讨了在半导体精密制造中,如何运用天棚阻尼PID控制算法实现VC-C级振动抑制。通过构建高保真单自由度仿真模型,详细解析了从算法原理、SIMULINK实现到参数整定的全链路方案,并针对谐波扰动提供了陷波滤波器设计方法,为工程师提供了从仿真到实践的完整主动隔振技术指南。

pz89012345的博客 541

深入理解Android之自定义View

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

Fighting_初心的博客 1万+

目标检测 | R-CNN、Fast R-CNN与Faster R-CNN理论讲解

目标检测:R-CNN、Fast R-CNN与Faster R-CNN理论讲解,根据B站up霹雳吧啦Wz总结。此文章包含R-CNN算法原理讲解、Fast R-CNN算法原理讲解、Faster R-CNN原理讲解、目标检测评价指标介绍与特征金字塔网络FPN讲解。

houjingchuan的博客 9572

32、AI责任认定:法律与社会视角的探讨

本文探讨了人工智能(AI)在法律与社会视角下的责任认定问题。从疏忽与责任标准、严格责任制度的适用性,到AI是否能够承担法律责任等角度展开分析,指出随着AI技术的发展,传统的责任概念需要重新审视。文章进一步将AI与法人进行类比,提出AI作为物理-社会系统的责任认定框架,并强调需要建立适应AI特点的法律体系以实现公平与安全的平衡。

star5的专栏 200

自定义View Measure过程 详解

自定义View Measure过程 详解前言自定义View measure过程详解3.1 单一View的measure过程3.1.1 应用场景3.1.2 具体流程3.1.3 总结3.2 ViewGroup的measure过程 前言 Acty 自定义View measure过程详解 measure过程 根据View的类型分为2种情况: 接下来,我将详细分析这两种measure过程 3.1 单一View的measure过程 3.1.1 应用场景 在无现成的控件View满足需求、需自己实现时,则使用自定义单一V

凉拌西红柿 322

YOLO26 网络结构逐模块解析:Backbone/Neck/Head

本文解析了YOLO26目标检测网络的架构设计,重点介绍其在Backbone、Neck和Head三个核心组件的创新优化。Backbone采用改进的CSP模块与轻量级注意力机制,提升特征提取效率;Neck使用改进的BiFPN结构实现高效多尺度特征融合;Head采用完全解耦设计简化回归流程。文章详细分析了各模块的技术演进、实现细节和设计思想,通过流程图和代码示例展示了YOLO26的整体架构和关键组件实现。该研究对模型优化、定制开发和硬件部署具有重要指导意义。

一键难忘的博客 1248

Android -- 自定义 View XML属性详解

1. View的构造函数 自定义View必须重写至少一个构造函数 // 如果View是在Java代码里面new的,则调用第一个构造函数 public CustomView7(Context context) { super(context); } // 如果View是在.xml里声明的,则调用第二个构造函数 public CustomView...

一只驴在敲代码 4064

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

《Android 自定义View的详解》

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

weijiangbc0的博客 1401

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

自定义View之onDraw方法详解

关于Android自定义View onDraw()方法详解,请出门右转: 参看启舰讲师的onDraw方法详解大全

刘淏卿的专栏 921

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Android自定义View的详解

转载请注明出处:http://blog.youkuaiyun.com/guolin_blog 如果说要按类型来划分的话,自定义View的实现方式大概可以分为三种,自绘控件、组合控件、以及继承控件。那么下面我们就来依次学习一下,每种方式分别是如何自定义View的。 一、自绘控件 自绘控件的意思就是,这个View上所展现的内容全部都是我们自己绘制出来的。绘制的代码是写在onDraw()方法中的,

问心 1350

Android自定义View详解,自定义View的实现方法

在Android中,所有的用户界面都是通过View来实现的,它是Android UI框架的核心组件。自定义View是在已有的View的基础上进行扩展和定制,所以在实现自定义View之前,我们首先需要了解View的基本知识。另一种方式是直接创建自定义View类,这样可以更加灵活地控制View的行为和样式。通过自定义View类,可以实现各种独特的用户界面效果和交互方式,比如自定义进度条、翻页效果、拖拽控件等。@Override。

m0_46368082的博客 2217

Android自定义 View 三核心方法详解

本文介绍了Android自定义View开发中三个核心方法的使用(onMeasur、onLayout、onDraw)

Meteors.的博客 369

Android 自定义View 详解

Android 自定义View 详解。

m0_59095127的博客 1687

Android自定义View详解

一、自定义View基础 1、分类 自定义View的实现方式有以下几种: 自定义组合控件: 多个控件组合成为一个新的控件,方便多处复用 继承系统View控件: 继承自TextView等系统控件,在系统控件的基础功能上进行扩展 继承View: 不复用系统控件逻辑,继承View进行功能定义 继承系统ViewGroup: 继承自LinearLayout等系统控件,在系统控件的基础功能上进行扩展 继承V...

恬静释然的博客 1013

Android的自定义view详解及Measurepec详解

自定义View的三大核心流程包括测量(Measure)、布局(Layout)和绘制(Draw)。测量阶段通过onMeasure方法确定视图尺寸,布局阶段通过onLayout确定位置,绘制阶段利用onDraw完成视觉呈现。关键要点:正确处理MeasureSpec测量模式(EXACTLY/AT_MOST/UNSPECIFIED),重写onMeasure时需考虑wrap_content情况并设置默认尺寸,在onDraw中应避免对象创建和考虑padding值。进阶技巧包括性能优化、自定义属性实现和触摸事件处理。掌握

weixin_41553587的博客 966

Android自定义view详解

从继承开始 懂点面向对象语言知识的都知道:封装,继承和多态,这是面向对象的三个基本特征,所以在自定义View的时候,最简单的方法就是继承现有的View 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class SketchView extends View{ public SketchView(Context

yanglin10000的博客 568

自定义View Layout过程 详解

自定义View Layout过程 详解前言1. 作用2. 知识储备3. layout过程详解3.1 单一View的layout过程3.1.1 应用场景3.1.2 具体流程3.1.3 总结3.2 ViewGroup的layout过程3.1.1 应用场景3.1.2 原理(步骤)3.1.3 具体流程3.1.3 总结 前言 Acty 1. 作用 计算视图(View)的位置 即计算View的四个顶点位置:Left、Top、Right 和 Bottom 2. 知识储备 请参考文章:自定义View基础 3. layo

凉拌西红柿 407
上一篇: AndroidStudio使用教程(第七弹)
下一篇: 通过Hardware Layer提高动画性能
CharonChui
博客等级 码龄12年 66粉丝 134原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值