Android自定义View学习笔记03

2024-06-24 03:58

本文主要是介绍Android自定义View学习笔记03,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Android自定义View学习笔记03

参考gitHub上面的开源项目CircleImageView

预备知识

BitMap

BitMap位图类,其中有一个嵌套类叫Bitmap.Config,内部有四个枚举值。这个类的作用是定义位图存储质量,即存储一个像素的位数,以及是否能显示透明、半透明颜色(Possible bitmap configurations. A bitmap configuration describes how pixels are stored. This affects the quality (color depth) as well as the ability to display transparent/translucent colors)。

A 透明度 R 红色 G 绿色 B 蓝色
Bitmap.Config ALPHA_8 只存储透明度,其他颜色不存储
Bitmap.Config ARGB_4444 16 每个像素 占四位
Bitmap.Config ARGB_8888 32 每个像素 占八位
Bitmap.Config RGB_565 16 R占5位 G占6位 B占5位 没有透明度(A)
参考博客

矩形类
  1. Rect类,通过定义四条(左、上、右、下)边来组成一个矩形。别扭的是,参数代表的是每条边距离x轴或者y轴的距离。
  2. RectF类,同上,不同的是Rect的参数类型的整形,带F的是单精度浮点数。另外Rect对象可以作为参数来构造RectF对象。
一个很重要的类BitmapShader位图渲染器

BitmapShader位图渲染器,用位图当做纹理来画图,通过设置模式来设置不同的渲染效果(Shader used to draw a bitmap as a texture. The bitmap can be repeated or mirrored by setting the tiling mode)。

  • 构造方法BitmapShader(Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY),参数分别是,当做纹理的位图、横轴方向的渲染方式、纵轴方向的渲染方式。
  • 有个嵌套类Shader.TileMode 瓦片模式,里面定义了三个枚举类型的变量。

CLAMP 如果渲染器超出了原始边界范围,会复制原始边界的颜色在范围外渲染。
REPEAT如果显示范围大于图片的大小,则会横向和纵向的重复渲染图片,进行平铺。
MIRROR和上一个类似,只不过是用镜像方式进行平铺。
一般用的时候会用第一个参数。

  • 设置变形矩阵的方法public void setLocalMatrix(Matrix localM)
  • 参考博客Android学习笔记进阶16之BitmapShader
Paint

该类内有一个设置Shader渲染器对象的方法public Shader setShader(Shader shader)来设置渲染器。

Matrix矩阵类

Matrix内部存有一个3*3的矩阵,里面存有变形信息,没有构造函数。只能通过具体的方法来设置相关参数。这个矩阵被分为四部分,分别是比例旋转、平移、等比例变换、透视变换。运算原理就是矩阵的变换,参考Android Matrix理论与应用详解
- 设置缩放的方法public void setScale(float sx,float sy),参数是水平缩放比例和竖直缩放比例。

代码

//CustomRoundImageView.java
package mmrx.com.myuserdefinedview.textview;import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.widget.ImageView;import mmrx.com.myuserdefinedview.R;/*** Created by mmrx on 2015/4/16.*/
public class CustomRoundImageView extends ImageView {private Context mContext;private Bitmap mBitmap;//图片画笔和边框画笔private Paint mBitMapPaint;private Paint mBorderPaint;//边框颜色private int mBorderColor = Color.WHITE;//边框宽度private float mBorderWidth = 0f;//渲染器private BitmapShader mBitMapShader;//图片拉伸方式 默认为按比例缩放private int mImageScale = 1;//控件的长宽private int mWidth;private int mHeight;//边框和圆形图片的半径private float mBorderRadius;private float mDrawableRadius;//矩形private RectF mDrawableRect;private RectF mBorderRect;//变换矩形private Matrix mBitMapMatrix;public CustomRoundImageView(Context mContext){super(mContext);this.mContext = mContext;}public CustomRoundImageView(Context mContext,AttributeSet attr){this(mContext, attr, R.attr.CustomImageView03Style);this.mContext = mContext;}public CustomRoundImageView(Context mContext,AttributeSet attr,int defSytle){super(mContext,attr,defSytle);TypedArray ta = mContext.obtainStyledAttributes(attr,R.styleable.CustomRoundImageView,defSytle,R.style.CustomizeStyle03);int count = ta.getIndexCount();for(int i=0;i<count;i++){int index = ta.getIndex(i);switch (index){case R.styleable.CustomRoundImageView_borderColor:mBorderColor = ta.getColor(index,Color.WHITE);break;case R.styleable.CustomRoundImageView_borderWidth:mBorderWidth = ta.getDimension(index,0f);break;case R.styleable.CustomRoundImageView_image:mBitmap = BitmapFactory.decodeResource(getResources(),ta.getResourceId(index,0));break;case R.styleable.CustomRoundImageView_imageScaleType:mImageScale = ta.getInt(index,0);break;default:break;}}ta.recycle();mBorderPaint = new Paint();mBitMapPaint = new Paint();mDrawableRect = new RectF();mBorderRect = new RectF();}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);int heightMod = MeasureSpec.getMode(heightMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);int widthMod = MeasureSpec.getMode(widthMeasureSpec);int widthSize = MeasureSpec.getSize(widthMeasureSpec);//march_parent & exactly dimenif(heightMod == MeasureSpec.EXACTLY){mHeight = heightSize;}//wrap_content & otherselse{mHeight = mBitmap.getHeight();}if(widthMod == MeasureSpec.EXACTLY){mWidth = widthSize;}//wrap_content & otherselse{mWidth = mBitmap.getWidth();}Log.v("--wrap_content--dimen1", "width: " + mWidth + " height: " + mHeight);//获得屏幕的宽高,代码里面获取和设置的都是像素作为单位DisplayMetrics dm = new DisplayMetrics();((Activity)mContext).getWindowManager().getDefaultDisplay().getMetrics(dm);int screenWidth = dm.widthPixels;int screenHeight = dm.heightPixels;//比较图片的尺寸和屏幕的尺寸,尺寸最大不得超出屏幕mWidth = mWidth<=screenWidth? mWidth : screenWidth;mHeight = mHeight<=screenHeight? mHeight : screenHeight;
//        Log.v("--wrap_content--dimen2","screenHeight: "+ screenHeight + " screenWidth: " + screenWidth);
//        Log.v("--wrap_content--dimen1","width: "+ mWidth + " height: " + mHeight);mWidth = Math.min(mWidth,mHeight);mHeight = Math.min(mWidth,mHeight);setMeasuredDimension(mWidth, mHeight);}@Overrideprotected void onDraw(Canvas canvas) {
//        super.onDraw(canvas);if(mBitmap == null)return;//设置渲染器mBitMapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);//抗锯齿mBitMapPaint.setAntiAlias(true);//设置渲染器mBitMapPaint.setShader(mBitMapShader);//设置外框的相关参数mBorderPaint.setStyle(Paint.Style.STROKE);mBorderPaint.setAntiAlias(true);mBorderPaint.setColor(mBorderColor);mBorderPaint.setStrokeWidth(mBorderWidth);//边框矩形mBorderRect.set(0,0,getWidth(),getHeight());//边框半径,考虑线条的宽度,如果没有考虑线条宽度,显示会把线条的一部分遮蔽mBorderRadius = Math.min((mBorderRect.width()-mBorderWidth)/2,(mBorderRect.height()-mBorderWidth)/2);//图片的矩形,因为图片是在外边框内部,所以位置矩形的坐标要考虑到边框的宽度mDrawableRect.set(mBorderWidth,mBorderWidth,mBorderRect.width()-mBorderWidth,mBorderRect.height()-mBorderWidth);//圆形图片的半径mDrawableRadius = mBorderRadius-mBorderWidth;//设置图片的缩放setBitMapScale();canvas.drawCircle(getWidth()/2,getHeight()/2,mDrawableRadius,mBitMapPaint);if(mBorderWidth != 0)canvas.drawCircle(getWidth()/2,getHeight()/2,mBorderRadius,mBorderPaint);}//根据控件的尺寸和设置的图片缩放模式,来对图片进行缩放private void setBitMapScale(){float scaleX = 0,scaleY = 0;//获得圆形的直径和图片的尺寸float diameter = mDrawableRadius*2;float mBitMapWidth = mBitmap.getWidth();float mBitMapHeight = mBitmap.getHeight();mBitMapMatrix = new Matrix();mBitMapMatrix.set(null);//fillXY 宽高单独缩放if(mImageScale == 0){scaleX = diameter/mBitMapWidth;scaleY = diameter/mBitMapHeight;}//center 等比例缩放else{float scale = 0;scaleX = diameter/mBitMapWidth;scaleY = diameter/mBitMapHeight;//如果宽度和高度至少有一个需要放大if(scaleX > 1 || scaleY > 1){scale = Math.max(scaleX,scaleY);}else{scale = Math.min(scaleX, scaleY);}scaleX = scale;scaleY = scale;}mBitMapMatrix.setScale(scaleX,scaleY);mBitMapShader.setLocalMatrix(mBitMapMatrix);}//设置图片public void setImage(Bitmap bm){this.mBitmap = bm;invalidate();}//设置图片public void setImage(int rid){this.mBitmap = BitmapFactory.decodeResource(getResources(),rid);invalidate();}//像素转换为dp
//    private int px2dip(Context context, float pxValue){
//        final float scale = context.getResources().getDisplayMetrics().density;
//        return (int)(pxValue * scale + 0.5f);
//    }
}

相关xml文件

<!-- activity_main.xml-->
<mmrx.com.myuserdefinedview.textview.CustomRoundImageView
            android:layout_width="200dp"android:layout_height="200dp"customview:borderWidth="5dp"customview:borderColor="#ff868686"customview:image="@drawable/hello"/>
<!-- attrs.xml--><attr name="image" format="reference"/><attr name="imageScaleType"><enum name="fillXY" value="0"/><enum name="center" value="1"/></attr><attr name="borderWidth" format="dimension"/><attr name="borderColor" format="color"/><declare-styleable name="CustomRoundImageView"><attr name="image"/><attr name="borderWidth"/><attr name="borderColor"/><attr name="imageScaleType"/></declare-styleable>
<!-- styles.xml--><style name="AppTheme" <item name="CustomImageView03Style">@style/CustomizeStyle03</item></style><style name="CustomizeStyle03"><item name="imageScaleType">center</item><item name="image">@drawable/ic_launcher</item><item name="borderWidth">0dp</item><item name="borderColor">#ffffffff</item></style>

有图有内啥

真机器图片

一些补充

  1. 我参考了文章开头提到的那个开源项目,同样继承了ImageView,也同样没有重写onMeasure方法,于是在xml文件中使用这个自定义ImageView的时候,报错,提示我android:layout_height没有wrap_content这个属性。当时百思不得其解,参考的其他的圆形ImageView都没有重写onMeasure方法,怎么他们的就好使?一番对比之后,发现在xml中设置图片的时候,都是使用的android:src设置图片,而我则是使用的自定义的属性来设置、获取图片。因此需要重写onMeasure方法来计算图片的大小。
  2. onMeasure方法中,当高度宽度属性设置为wrap_content时,有可能因为图片本身过大,显示的时候在屏幕上显示不完整。因此我在onMeasure方法中增加了一个与屏幕尺寸的对比,图片的尺寸不得超出屏幕的尺寸。在这个过程中,发现onMeasure中设置的尺寸都是以像素为单位的(当我将像素和dip之间进行转换后在填充到setMeasuredDimension方法中后,实际效果并不是我想象中的样子)。
  3. 不管是参考代码中还是我自己写的代码中,有好几个Rect或者RectF类,但是在最终的绘图过程中却都没有被用到。之前我也不理解这些矩形有什么用处,但是在思考和写代码的过程中,逐渐理解了其中的原理:这些矩形是用来表示范围数据的,就好像在做月饼时候的月饼模子一样。里面包含了一些尺寸、位置数据。在上面的代码中,矩形被用于计算圆形半径、计算图片缩放比例。
  4. 实现圆形ImageView有好几种不同的方式,上面使用Shader渲染器只是其中的一种,还有一种是用图片遮挡的方式来进行表示。
  5. 本来打算完全自己写的…提笔了还是感觉很多东西是模模糊糊的,结论,模糊的东西还是没有被完全转化为自己的知识储备,仍然需要多多练习,用代码量和时间来巩固。

发现问题

如果该控件在Fragment的布局文件中中被使用,下面这句话会在编译时报错((Activity)mContext).getWindowManager().getDefaultDisplay().getMetrics(dm);原因是在Fragment中需要调用getActivity()来获取所在的Activity,暂时未找到解决方法…所以只能把获得屏幕尺寸的那部分注释掉,在布局文件中只能定义具体数值,才能在Fragment的布局文件中中被使用。

更新时间2015年5月3日10:27:02

问题:
自定义控件无法再Fragment的布局文件中使用。
解决:
private Context mContext;这行代码去掉,同时去掉其他代码中对mContext变量的引用;在CustomRoundImageView(Context mContext,AttributeSet attr,int defSytle)方法中将TypedArray ta = mContext.obtainStyledAttributes(attr,R.styleable.CustomRoundImageView,defSytle,R.style.CustomizeStyle03);这句话修改为TypedArray ta = context.getTheme().obtainStyledAttributes(attr,R.styleable.CustomRoundImageView,defSytle,R.style.CustomizeStyle03);

之前的相关博文

Android自定义view学习笔记01
Android自定义view学习笔记02
Android自定义view学习笔记04

源码同步到github

这篇关于Android自定义View学习笔记03的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1089106

相关文章

51单片机学习记录———定时器

文章目录 前言一、定时器介绍二、STC89C52定时器资源三、定时器框图四、定时器模式五、定时器相关寄存器六、定时器练习 前言 一个学习嵌入式的小白~ 有问题评论区或私信指出~ 提示:以下是本篇文章正文内容,下面案例可供参考 一、定时器介绍 定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。 定时器作用: 1.用于计数系统,可

问题:第一次世界大战的起止时间是 #其他#学习方法#微信

问题:第一次世界大战的起止时间是 A.1913 ~1918 年 B.1913 ~1918 年 C.1914 ~1918 年 D.1914 ~1919 年 参考答案如图所示

[word] word设置上标快捷键 #学习方法#其他#媒体

word设置上标快捷键 办公中,少不了使用word,这个是大家必备的软件,今天给大家分享word设置上标快捷键,希望在办公中能帮到您! 1、添加上标 在录入一些公式,或者是化学产品时,需要添加上标内容,按下快捷键Ctrl+shift++就能将需要的内容设置为上标符号。 word设置上标快捷键的方法就是以上内容了,需要的小伙伴都可以试一试呢!

Tolua使用笔记(上)

目录   1.准备工作 2.运行例子 01.HelloWorld:在C#中,创建和销毁Lua虚拟机 和 简单调用。 02.ScriptsFromFile:在C#中,对一个lua文件的执行调用 03.CallLuaFunction:在C#中,对lua函数的操作 04.AccessingLuaVariables:在C#中,对lua变量的操作 05.LuaCoroutine:在Lua中,

AssetBundle学习笔记

AssetBundle是unity自定义的资源格式,通过调用引擎的资源打包接口对资源进行打包成.assetbundle格式的资源包。本文介绍了AssetBundle的生成,使用,加载,卸载以及Unity资源更新的一个基本步骤。 目录 1.定义: 2.AssetBundle的生成: 1)设置AssetBundle包的属性——通过编辑器界面 补充:分组策略 2)调用引擎接口API

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

大学湖北中医药大学法医学试题及答案,分享几个实用搜题和学习工具 #微信#学习方法#职场发展

今天分享拥有拍照搜题、文字搜题、语音搜题、多重搜题等搜题模式,可以快速查找问题解析,加深对题目答案的理解。 1.快练题 这是一个网站 找题的网站海量题库,在线搜题,快速刷题~为您提供百万优质题库,直接搜索题库名称,支持多种刷题模式:顺序练习、语音听题、本地搜题、顺序阅读、模拟考试、组卷考试、赶快下载吧! 2.彩虹搜题 这是个老公众号了 支持手写输入,截图搜题,详细步骤,解题必备

《offer来了》第二章学习笔记

1.集合 Java四种集合:List、Queue、Set和Map 1.1.List:可重复 有序的Collection ArrayList: 基于数组实现,增删慢,查询快,线程不安全 Vector: 基于数组实现,增删慢,查询快,线程安全 LinkedList: 基于双向链实现,增删快,查询慢,线程不安全 1.2.Queue:队列 ArrayBlockingQueue:

ROS话题通信流程自定义数据格式

ROS话题通信流程自定义数据格式 需求流程实现步骤定义msg文件编辑配置文件编译 在 ROS 通信协议中,数据载体是一个较为重要组成部分,ROS 中通过 std_msgs 封装了一些原生的数据类型,比如:String、Int32、Int64、Char、Bool、Empty… 但是,这些数据一般只包含一个 data 字段,结构的单一意味着功能上的局限性,当传输一些复杂的数据,比如:

硬件基础知识——自学习梳理

计算机存储分为闪存和永久性存储。 硬盘(永久存储)主要分为机械磁盘和固态硬盘。 机械磁盘主要靠磁颗粒的正负极方向来存储0或1,且机械磁盘没有使用寿命。 固态硬盘就有使用寿命了,大概支持30w次的读写操作。 闪存使用的是电容进行存储,断电数据就没了。 器件之间传输bit数据在总线上是一个一个传输的,因为通过电压传输(电流不稳定),但是电压属于电势能,所以可以叠加互相干扰,这也就是硬盘,U盘