Android自定义View的实现总结
2015-12-05 13:52
971 查看
Android自定义View的实现总结
说之前先看下如下代码:public abstract class [ViewGroup][6] extends [View][6] { ...} public class [RelativeLayout][6] extends [ViewGroup][6] { ...} public class [LinearLayout][6] extends [ViewGroup][6] { ...} public class [TextView][6] extends [View][6] { ... } ...
从以上代码可以知道:
- ViewGroup是View的子类
-View类的子类有TextView,EditView这种文本框
-ViewGroup类的子类有Linearlayout,Slidingmenu等布局
自定义VIew
适当地使用VIew可以丰富应用程序的体验效果,反正你懂的。所以熟练掌握是非常有必要的。在自定义VIew时,我们通常必须重写onDraw()方法来绘制View显示的内容。如果改View还需要使用wrap_content属性,那么还需要重写onMeasure()方法。另外,通过自定义attrs属性,还可以设置新的属性配置。在View中通常会以下几个重要的回调方法:
- onFinishInflate(): 从xml加载组件后回调。
- onSizeChangeed():组件大小改变时回调。
- onMeasure():回调该方法进行测量。
- onLayout():回调该方法来确定显示的位置。
- onTouchEvent():监听到触摸事件时回调。
通常我们如果需要对现有控件进行拓展,通过组件来实现全新控件,重写VIew来实现全新的控件。
onMeasure()方法
对view进行测量,告诉系统画一个多大的view。传入的两个参数是由上一层控件传入的大小,有多种情况,重写该方法需要技术控件的实际大小,然后调用setMeasuredDimension(int, int)设置实际大小。如下:
@Override protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) { mMeasureWidth = MeasureSpec.getSize(widthMeasureSpec); mMeasureHeigth = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(mMeasureWidth, mMeasureHeigth); }
onMeasure传入的 widthMeasureSpec和heightMeasureSpec不是一般的尺寸数值,而是将模式和尺寸组合在一起的数值。
通过 int mode =MeasureSpec.getMode(widthMeasureSpec)得到模式。
通过 int size = MeasureSpec.getSize(widthMeasureSpec)得到尺寸。
测量的模式分为以下三种:
EXACTLY
精确模式,当控件layout_width属性或layout_height属性为具体数值时或者指定为match_parent属性时(占据父View的大小)系统使用的是这种模式。
AT_MOST
最大值模式,当控件layout_width属性或layout_height属性为wrap_content时,控件大小一般随着控件的子控件或内容的变化而变化,此时控件的尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。
UNSPECIFIED
不指定其测量模式,View想多大就多大,通常在绘制自定义view时才使用。
在重写onMeasure方法时要根据模式不同进行尺寸计算,看一个典型例子:
布局相对简单:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.example.zyzhang.ConView android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
@Override protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) { Log.d("zyzhang", "onMeasure()"); setMeasuredDimension( measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); } private int measureWidth(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { result = specSize; } else { // UNSPECIFIED result = 200; if (specMode == MeasureSpec.AT_MOST) { result = Math.min(result, specSize); } } return result; }
具体分析:measureHeight()和measureWidth()方法基本一致,通过这两个方法我们就完成了宽高的自定义。
-当布局文件中先指定宽为固定值(如400dp),为固定大小;
-当指定宽高为match_parent属性值是,填充整个布局;
- 当指定宽高为wrap_content属性时,如果不重写onMeasure()方法,那么系统不知道该使用默认多大的尺寸。因此,它会默认填充整个布局,所以重写onMeasure()方法的目的就是为了能够给view一个wrap_content属性下的默认大小(如: result = 200;)。
简单实例1
效果图:具体实现
package com.zyzhang.administrator.radar; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Shader; import android.graphics.SweepGradient; import android.util.AttributeSet; import android.view.View; /** * Created by zyzhang on 2015/12/1. * 邮箱:zhangtaihu101@163.com */ public class RadarView extends View { private int w; private int h; private Paint linePaint; private Paint sectorPaint; private Matrix matrix; private int i = 0; private android.os.Handler handler = new android.os.Handler(); private Runnable run = new Runnable() { @Override public void run() { i = i + 2; matrix.postRotate(i, w / 2, h / 2); RadarView.this.invalidate(); handler.postDelayed(run, 50); } }; public RadarView(Context context, AttributeSet attrs) { super(context, attrs); //获取屏幕宽高 w = context.getResources().getDisplayMetrics().widthPixels; h = context.getResources().getDisplayMetrics().heightPixels; init(); //启动线程 handler.post(run); } /*初始化*/ private void init() { linePaint = new Paint(); linePaint.setColor(Color.parseColor("#A1A1A1")); linePaint.setStrokeWidth(4); linePaint.setAntiAlias(true);// 设置抗锯齿 linePaint.setStyle(Paint.Style.STROKE); sectorPaint = new Paint(); sectorPaint.setColor(0x9D00FF00); sectorPaint.setAntiAlias(true); matrix = new Matrix(); } /** * 测量控件的宽高 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 设置此视图的宽高 setMeasuredDimension(w, h); } @Override protected void onDraw(Canvas canvas) { canvas.drawCircle(w / 2, h / 2, w / 6, linePaint); canvas.drawCircle(w / 2, h / 2, 2*w / 6, linePaint); canvas.drawCircle(w / 2, h / 2, 11 * w / 20, linePaint); canvas.drawCircle(w / 2, h / 2, 7*h / 16, linePaint); Shader shader = new SweepGradient(w / 2, h / 2, Color.TRANSPARENT, Color.parseColor("#AAAAAAAA")); sectorPaint.setShader(shader); canvas.concat(matrix); canvas.drawCircle(w / 2, h / 2, 7*h / 16, sectorPaint); matrix.reset(); super.onDraw(canvas); } }
布局文件
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.zyzhang.administrator.radar.RadarView android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/radar_bg"/> <ImageView android:id="@+id/imageView" android:layout_width="100dp" android:layout_height="100dp" android:layout_gravity="center" android:src="@drawable/xyq"/> </FrameLayout>
简单实例2(自定义圆形进度条)
效果图:
具体实现:
1布局文件:
activity_main.xml<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <RelativeLayout android:id="@+id/relativeLayout1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:text="系统自带progressbar" /> <ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:indeterminateDrawable="@drawable/frame_loading" /> <ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:indeterminateDrawable="@drawable/rotate_loading_github" android:indeterminateDuration="1800" /> </RelativeLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/relativeLayout1" android:layout_centerHorizontal="true" android:layout_marginTop="40dp" > <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:text="自定义(剪辑(clip)图像)" /> <com.example.customprogressbar.CustomClipLoading android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_marginLeft="16dp" android:layout_toRightOf="@+id/textView1" > </com.example.customprogressbar.CustomClipLoading> </RelativeLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" > <com.example.customprogressbar.ProgressWheel xmlns:ProgressWheel="http://schemas.android.com/apk/res-auto" android:id="@+id/progresswheel_bar" android:layout_width="80dp" android:layout_height="80dp" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" ProgressWheel:barColor="#339BB9" ProgressWheel:barWidth="4dp" ProgressWheel:rimColor="#44000000" ProgressWheel:rimWidth="4dp" ProgressWheel:spinSpeed="3dp" ProgressWheel:text="click" ProgressWheel:textColor="#222222" ProgressWheel:textSize="14sp" /> <Button android:id="@+id/progresswheel_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:text="开始" /> </RelativeLayout> </RelativeLayout>
custom_loading.xml
<?xml version="1.0" encoding="utf-8"?> <ImageView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/iv_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:background="@drawable/loading_bg" android:paddingLeft="3dp" android:paddingTop="3dp" android:scaleType="centerInside" android:src="@drawable/clip_loading" />
资源文件
attrs.xml<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="ProgressWheel"> <attr name="text" format="string" /> <attr name="textColor" format="color" /> <attr name="textSize" format="dimension" /> <attr name="barColor" format="color" /> <attr name="rimColor" format="color" /> <attr name="rimWidth" format="dimension" /> <attr name="spinSpeed" format="dimension" /> <attr name="delayMillis" format="integer" /> <attr name="circleColor" format="color" /> <attr name="radius" format="dimension" /> <attr name="barWidth" format="dimension" /> <attr name="barLength" format="dimension" /> </declare-styleable> <!-- <declare-styleable name="PieProgress"> <attr name="foregroundColor" format="color" /> <attr name="backgroundColor" format="color" /> </declare-styleable> --> </resources>
自定义view类的实现 (关键部分)
ProgressWheel.javapackage com.example.customprogressbar; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.RectF; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.view.View; /** * @author zyzhang * @date 2015-12-5 下午4:33:50 */ public class ProgressWheel extends View { // 设置默认值 private int layout_height = 0; private int layout_width = 0; private int barWidth = 20; private int rimWidth = 20; private int barLength = 60; private int textSize = 20; private int fullRadius = 100; private int circleRadius = 80; private int textColor = 0xFF000000; private int barColor = 0xAA000000; private int circleColor = 0x00000000; private int rimColor = 0xAADDDDDD; // Padding (with defaults) private int paddingTop = 5; private int paddingBottom = 5; private int paddingLeft = 5; private int paddingRight = 5; // The amount of pixels to move the bar by on each draw private int spinSpeed = 2; // 旋转速度 // The number of milliseconds to wait inbetween each draw private int delayMillis = 0; // Paints private Paint barPaint = new Paint(); private Paint circlePaint = new Paint(); private Paint rimPaint = new Paint(); private Paint textPaint = new Paint(); // Rectangles // private RectF rectBounds = new RectF(); private RectF circleBounds = new RectF(); int progress = 0; boolean isSpinning = false; // Other private String text = ""; private String[] splitText = {}; private Handler spinHandler = new Handler() { /** * This is the code that will increment the progress variable and so * spin the wheel */ @Override public void handleMessage(Message msg) { invalidate(); if (isSpinning) { progress += spinSpeed; if (progress > 360) { progress = 0; } spinHandler.sendEmptyMessageDelayed(0, delayMillis); } // super.handleMessage(msg); } }; /** * The constructor for the ProgressWheel * * @param context * @param attrs */ public ProgressWheel(Context context, AttributeSet attrs) { super(context, attrs); parseAttributes(context.obtainStyledAttributes(attrs, R.styleable.ProgressWheel)); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // Share the dimensions layout_width = w; layout_height = h; setupPaints(); setupBounds(); invalidate(); } /** * Set the properties of the paints we're using to draw the progress wheel * 设置属性 描绘我们使用画轮的进度 */ private void setupPaints() { // 进度 条 和 边 bar rim barPaint.setColor(barColor); barPaint.setAntiAlias(true); barPaint.setStyle(Style.STROKE);// 空心 barPaint.setStrokeWidth(barWidth); rimPaint.setColor(rimColor); rimPaint.setAntiAlias(true); rimPaint.setStyle(Style.STROKE); rimPaint.setStrokeWidth(rimWidth); circlePaint.setColor(circleColor); circlePaint.setAntiAlias(true); circlePaint.setStyle(Style.FILL); textPaint.setColor(textColor); textPaint.setStyle(Style.FILL); textPaint.setAntiAlias(true); textPaint.setTextSize(textSize); } /** * Set the bounds of the component 设置组件的边界 */ private void setupBounds() { // 宽度应该等于高度, 最小值 int minValue = Math.min(layout_width, layout_height); // Calc偏移量(可选) int xOffset = layout_width - minValue; int yOffset = layout_height - minValue; // Add the offset paddingTop = this.getPaddingTop() + (yOffset / 2); paddingBottom = this.getPaddingBottom() + (yOffset / 2); paddingLeft = this.getPaddingLeft() + (xOffset / 2); paddingRight = this.getPaddingRight() + (xOffset / 2); // rectBounds = new RectF(paddingLeft, paddingTop, // this.getLayoutParams().width - paddingRight, // this.getLayoutParams().height - paddingBottom); circleBounds = new RectF(paddingLeft + barWidth, paddingTop + barWidth, this.getLayoutParams().width - paddingRight - barWidth, this.getLayoutParams().height - paddingBottom - barWidth); fullRadius = (this.getLayoutParams().width - paddingRight - barWidth) / 2; circleRadius = (fullRadius - barWidth) + 1; } /** * Parse the attributes passed to the view from the XML * * @param a * the attributes to parse */ private void parseAttributes(TypedArray a) { barWidth = (int) a.getDimension(R.styleable.ProgressWheel_barWidth, barWidth); rimWidth = (int) a.getDimension(R.styleable.ProgressWheel_rimWidth, rimWidth); spinSpeed = (int) a.getDimension(R.styleable.ProgressWheel_spinSpeed, spinSpeed); delayMillis = (int) a.getInteger(R.styleable.ProgressWheel_delayMillis, delayMillis); if (delayMillis < 0) { delayMillis = 0; } barColor = a.getColor(R.styleable.ProgressWheel_barColor, barColor); barLength = (int) a.getDimension(R.styleable.ProgressWheel_barLength, barLength); textSize = (int) a.getDimension(R.styleable.ProgressWheel_textSize, textSize); textColor = (int) a.getColor(R.styleable.ProgressWheel_textColor, textColor); // if the text is empty , so ignore it if (a.hasValue(R.styleable.ProgressWheel_text)) { setText(a.getString(R.styleable.ProgressWheel_text)); } rimColor = (int) a.getColor(R.styleable.ProgressWheel_rimColor, rimColor); circleColor = (int) a.getColor(R.styleable.ProgressWheel_circleColor, circleColor); // 记得回收 a.recycle(); } /** * public void drawArc (RectF oval, float startAngle, float sweepAngle, * boolean useCenter, Paint paint) 参数1:圆弧所在的椭圆对象 2:起始角度 * 参数3:圆心角角度,360为圆,180为半圆 参数4:是否显示半径连线,true表示显示圆弧与圆心的半径连线,false表示不显示 * 参数5:画笔Paint */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Draw the rim canvas.drawArc(circleBounds, 360, 360, false, rimPaint); // Draw the bar if (isSpinning) {//打开的时候执行这个 canvas.drawArc(circleBounds, progress - 90, barLength, false, barPaint); } else {//点击按钮执行这个 两者效果不一样 canvas.drawArc(circleBounds, -90, progress, false, barPaint); } // Draw the inner circle 内圆 canvas.drawCircle((circleBounds.width() / 2) + rimWidth + paddingLeft, (circleBounds.height() / 2) + rimWidth + paddingTop, circleRadius, circlePaint); // Draw the text (attempts to center it horizontally and vertically) int offsetNum = 0; for (String s : splitText) { float offset = textPaint.measureText(s) / 2; canvas.drawText(s, this.getWidth() / 2 - offset, this.getHeight() / 2 + (textSize * (offsetNum)) - ((splitText.length - 1) * (textSize / 2)), textPaint); offsetNum++; } } /** * Puts the view on spin mode */ public void spin() { isSpinning = true; spinHandler.sendEmptyMessage(0); } /** * Set the text in the progress bar Doesn't invalidate the view * 设置进度条不失效中的文本视图 * * @param text * 文本显示 the text to show ('\n' constitutes a new line) */ public void setText(String text) { this.text = text; splitText = this.text.split("\n"); } public void resetCount() { progress = 0; setText("0%"); invalidate(); } public void incrementProgress() { isSpinning = false; progress++; setText(Math.round(((float) progress / 360) * 100) + "%"); spinHandler.sendEmptyMessage(0); } }
CustomClipLoading.java
package com.example.customprogressbar; import android.content.Context; import android.graphics.drawable.ClipDrawable; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; /** * @author zyzhang * @date 2015-12-5 下午4:09:14 */ public class CustomClipLoading extends FrameLayout { private static final int MAX_PROGRESS = 10000; //ClipDrawable代表从其它位图上截取一个"图片片段",XML中的根元素为<clip.../>, //截取的方向由clipOrientation控制 private ClipDrawable mClipDrawable; private int mProgress = 0; private boolean running; private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { // 如果消息是本程序发送的 if (msg.what == 0x123) { mClipDrawable.setLevel(mProgress); } } }; public CustomClipLoading(Context context) { this(context, null, 0); } public CustomClipLoading(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CustomClipLoading(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context) { View view = LayoutInflater.from(context).inflate(R.layout.custom_loading, null); addView(view); ImageView imageView = (ImageView) findViewById(R.id.iv_progress); mClipDrawable = (ClipDrawable) imageView.getDrawable(); Thread s = new Thread(r); s.start(); } Runnable r = new Runnable() { @Override public void run() { running = true; while (running) { handler.sendEmptyMessage(0x123); if (mProgress > MAX_PROGRESS) { mProgress = 0; } mProgress += 100; //设置停顿效果 try { Thread.sleep(18); } catch (InterruptedException e) { e.printStackTrace(); } } } }; //根据需要实现 public void stop() { mProgress = 0; running = false; } }
MainActivity.java
package com.example.customprogressbar; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class MainActivity extends Activity { private ProgressWheel pwOne; private boolean wheelRunning; private int wheelProgress = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); pwOne = (ProgressWheel) findViewById(R.id.progresswheel_bar); pwOne.spin(); Button startBtn = (Button) findViewById(R.id.progresswheel_btn); startBtn.setOnClickListener(new OnClickListener() { public void onClick(View v) { if (!wheelRunning) { wheelProgress = 0; pwOne.resetCount(); new Thread(r).start(); } } }); } final Runnable r = new Runnable() { public void run() { wheelRunning = true; while (wheelProgress < 361) { pwOne.incrementProgress(); wheelProgress++; try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } wheelRunning = false; } }; }
源码
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories