Android圆形头像的绘制(三)之多人头像的实现
16lz
2021-01-26
上篇文章Android圆形图像的绘制(二)介绍了单人圆形头像的绘制,这篇文章也是圆形头像的最后一篇。多人头像存在的场景有很多,像一些社交软件,只要涉及到群聊的功能,基本上都会存在多人头像。下面介绍多人头像是怎样实现的,最多支持五人。
在进行多人头像绘制的过程中,CircleImageView自定义控件中应该有个列表保存多人头像的相关信息,每个人的信息应该封装为一个bean对象,上篇文章介绍圆形头像涉及到位图和随机图像的展示,所以我们bean对象包含的数据有位图、随机背景、文本,代码如下:
public class CircleImageViewBean { /** * 位图 */ private Bitmap bitmap; /** * 随机背景 */ private int randomBg; /** * 文本信息 */ private String text; public CircleImageViewBean(Bitmap bitmap, int randomBg, String text) { this.bitmap = bitmap; this.randomBg = randomBg; this.text = text; } public int getRandomBg() { return randomBg; } public void setRandomBg(int randomBg) { this.randomBg = randomBg; } public String getText() { return text; } public void setText(String text) { this.text = text; } public Bitmap getBitmap() { return bitmap; } public void setBitmap(Bitmap bitmap) { this.bitmap = bitmap; }}
在定义好bean对象后,CircleImageView自定义控件应该为外面提供一个公用的方法,方法如下: /** * 设置多人头像 * @param circleImageViewBeanList */ public void setImageBitmaps(List circleImageViewBeanList) { mCircleImageViewBeanList = circleImageViewBeanList; invalidate(); }
接下来看下onDraw方法的实现,代码如下: private static final int MAX = 5;
protected void onDraw(Canvas canvas) { //一定要注释掉,否则圆形图像没法生效// super.onDraw(canvas); if (mWidth > 0 && mHeight > 0) {// Bitmap bitmap = createCircleBitmapForSRC_IN(canvas);// if (bitmap != null) {// canvas.drawBitmap(bitmap, 0, 0, new Paint());// } if (mCircleImageViewBeanList != null && mCircleImageViewBeanList.size() > 0) { mCount = Math.min(mCircleImageViewBeanList.size(), MAX); //绘制多人头像 createCircleBitmap(canvas); } } }
在绘制之前,需要获取当前多人头像为几人头像,最大值为5,状态用mCount变量保存。下面计算每个小圆相对于大圆的缩放比例,缩放比例的获取用到的是基本的三角函数的相关知识,代码如下: public float getScale(int count) { int angle = getAngle(count); if (angle == 360) { return 1f; } double cot = getCot(angle); float scale = (float) (1f / (Math.sqrt(1 + Math.pow(cot, 2)) + 1)); return scale; }
private static final int[] sAngleArray = {360, 90, 60, 45, 36}; private int getAngle(int count) { return count > 0 && count <= sAngleArray.length ? sAngleArray[count - 1] : null; }
private double getCot(int angle) { double radian = Math.toRadians(angle); double sin = Math.sin(radian); double cos = Math.cos(radian); return cos / sin; }
下面获取与大圆最顶部相切的小圆的左上角位置,主要是为了方便计算其他小圆的左上角的位置,因为找到这个小圆的对应的位置是在旋转角度为零的位置,如果想得到其他小圆的位置,只需要根据旋转角度,运用三角函数知识就可以得到。最上面小圆左上角位置获取的代码如下: public float[] getTopPosition(float radius, float scale) { float x = radius * (1 - scale); float y = 0; return new float[] { x, y }; }
然后,我们获取每个小圆左上角的位置,这里需要知道大圆半径、旋转角度、缩放比例、最上面小圆左上角的位置,旋转角度决定的小头像的摆放位置,它的值定义了一个数据,代码如下:
private static final float[][] sRotationArray = { new float[] { 360 }, new float[] { 45, -135 }, new float[] { 120, 0, -120 }, new float[] { 45, 135, -135, -45 }, new float[] { 144, 72, 0, -72, -144 }}; public float[] getRotation(int count) { return count > 0 && count <= sRotationArray.length ? sRotationArray[count - 1] : null; }
小头像位置计算的代码如下:
public float[] getTranslatePosition(float radius, float rotation, float scale, float topX, float topY) { float smallRadius = radius * (1 - scale); double radian = Math.toRadians(rotation); double cos = Math.cos(radian); double sin = Math.sin(radian); float x = (float) (topX - smallRadius * sin); float y = (float) (topY + smallRadius * (1 - cos)); return new float[] { x, y }; }
得到每个小圆左上角的位置后,需要对目标canvas进行相应的平移,为了能准备画出每个小圆展示在哪个位置,在进行变换之前,需要对目标canvas进行状态的保存,防止这里的变换操作,对其他小圆产生影响,代码如下: targetCanvas.save(); float[] radianPosition = mPresenter.getTranslatePosition(viewSize / 2, rotation[i], scale, topPosition[0], topPosition[1]); targetCanvas.translate(radianPosition[0], radianPosition[1]); targetCanvas.restore();
当canvas移动到具体的位置之后,我们需要获取位图,将其在canvas上绘制出来。先处理位图相关的绘制逻辑,因为位图的宽高,跟视图的宽高肯定是不一致的,此时需要对位图进行相应的缩放操作,这里涉及到两次缩放的逻辑,第一次是位图缩放到大圆的逻辑,第二次是大圆缩放到小圆的逻辑,代码如下: int width = bitmap.getWidth(); int height = bitmap.getHeight(); if (width > 0 && height > 0) { //对位图进行缩放 float scaleX = (float) mWidth / width; float scaleY = (float) mHeight / height; Matrix bitmapMatrix = new Matrix(); bitmapMatrix.postScale(scaleX, scaleY); bitmapMatrix.postConcat(matrix); newBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, bitmapMatrix, true); }
随机头像的绘制逻辑跟上篇文章的逻辑基本上是一样的,只是需要注意下,绘制的时候需要在小圆的基础上绘制,需要对字体的大小进行相应的缩放,代码如下: int size = (int) (viewSize * scale); private Bitmap createRandomMaskBitmap(int size, float scale, String text) { Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); final Paint paint = new Paint(); paint.setAntiAlias(true);// 抗锯齿 paint.setFilterBitmap(true); paint.setColor(mBackground); int center = size / 2;//获取画布的中心位置 //创建canvas对象,绘制随机背景 Canvas canvas = new Canvas(output); canvas.drawCircle(center, center, center, paint); //绘制随机背景上的文字 setText(canvas, size, paint, scale, text); return output; }
private void setText(Canvas canvas, int size, Paint paint, float scale, String text) { Rect targetRect = new Rect(0, 0, size, size); //设置绘制文本字体的颜色 paint.setColor(Color.WHITE); //设置绘制文本的大小 paint.setTextSize(mTextSize * scale); //获取文本展示的居中位置 Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt(); int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2; paint.setTextAlign(Paint.Align.CENTER); canvas.drawText(text, targetRect.centerX(), baseline, paint); }
到这里,多人头像已经基本是实现了,效果图如下:
单人头像的展示,跟上篇文章的效果图也是一样的,完整的代码如下:
CircleImageView.java
package com.dylan.circleimageview;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Bitmap;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Matrix;import android.graphics.Paint;import android.graphics.PorterDuff;import android.graphics.PorterDuffXfermode;import android.graphics.Rect;import android.graphics.drawable.BitmapDrawable;import android.graphics.drawable.Drawable;import android.util.AttributeSet;import android.widget.ImageView;import java.util.ArrayList;import java.util.List;/** * Description * author Dylan.zhuang * Date: 16/6/30-下午2:43 */public class CircleImageView extends ImageView { private static final String TAG = "CircleImageView"; private static final int MAX = 5; private static final float DEFAULT_SCALE = 0.9f; /** * 绘制图片的位图 */ private Bitmap mBitmap; /** * 圆形图像边框宽度 */ private int mStrokeWidth; /** * 圆形图像边框颜色 */ private int mStrokeColor; /** * 随机背景文本大小 */ private int mTextSize; /** * 随机背景颜色 */ private int mBackground; /** * 随机背景要展示的文本 */ private String mText; /** * 视图宽度 */ private int mWidth; /** * 视图高度 */ private int mHeight; /** * 存放多人头像的列表 */ private List mCircleImageViewBeanList; /** * 逻辑层 */ private CircleImageViewPresenter mPresenter; /** * 记录头像的个数 */ private int mCount; public CircleImageView(Context context) { this(context, null); } public CircleImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CircleImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //#5AC3B2 int defaultColor = getResources().getColor(R.color.colorGreen); //14sp int defaultTextSize = getResources().getDimensionPixelSize(R.dimen.dimen_default_text_size); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyleAttr, 0); mStrokeWidth = typedArray.getDimensionPixelSize(R.styleable.CircleImageView_stroke_width, 0); mStrokeColor = typedArray.getColor(R.styleable.CircleImageView_stroke_color, defaultColor); mTextSize = typedArray.getDimensionPixelSize(R.styleable.CircleImageView_text_size, defaultTextSize); mBackground = typedArray.getColor(R.styleable.CircleImageView_random_backgroud, defaultColor); mText = typedArray.getString(R.styleable.CircleImageView_text); //一定要记得回收 typedArray.recycle(); mPresenter = new CircleImageViewPresenter(); } /** * 设置边缘宽度 * * @param width */ public void setStrokeWidth(int width) { mStrokeWidth = width; } /** * 设置边缘颜色 * * @param color */ public void setStrokeColor(int color) { mStrokeColor = color; } /** * 设置文本大小 * * @param textSize */ public void setTextSize(int textSize) { mTextSize = textSize; } /** * 设置背景颜色 * * @param background */ public void setBackground(int background) { mBackground = background; } /** * 设置文本 * * @param text */ public void setText(String text) { mText = text; } /** * 绘制随机背景 */ public void drawRandomBackground() { readyInvalidate(mBitmap, mBackground, mText); } /** * 设置多人头像 * @param circleImageViewBeanList */ public void setImageBitmaps(List circleImageViewBeanList) { mCircleImageViewBeanList = circleImageViewBeanList; invalidate(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; } @Override public void setImageResource(int resId) { super.setImageResource(resId); mBitmap = getBitmapFromDrawable(getDrawable()); readyInvalidate(mBitmap, mBackground, mText); } @Override public void setImageDrawable(Drawable drawable) { super.setImageDrawable(drawable); mBitmap = getBitmapFromDrawable(drawable); readyInvalidate(mBitmap, mBackground, mText); } @Override public void setImageBitmap(Bitmap bm) { super.setImageBitmap(bm); mBitmap = bm; readyInvalidate(mBitmap, mBackground, mText); } @Override protected void onDraw(Canvas canvas) { //一定要注释掉,否则圆形图像没法生效// super.onDraw(canvas); if (mWidth > 0 && mHeight > 0) {// Bitmap bitmap = createCircleBitmapForSRC_IN(canvas);// if (bitmap != null) {// canvas.drawBitmap(bitmap, 0, 0, new Paint());// } if (mCircleImageViewBeanList != null && mCircleImageViewBeanList.size() > 0) { mCount = Math.min(mCircleImageViewBeanList.size(), MAX); //绘制多人头像 createCircleBitmap(canvas); } } } /** * 为调用onDraw之前准备数据 * @param bitmap * @param randomBg * @param text */ private void readyInvalidate(Bitmap bitmap, int randomBg, String text) { if (mCircleImageViewBeanList == null) { mCircleImageViewBeanList = new ArrayList(); } mCircleImageViewBeanList.clear(); CircleImageViewBean bean = new CircleImageViewBean(bitmap, randomBg, text); mCircleImageViewBeanList.add(bean); invalidate(); } /** * 创建圆形位图 * @param targetCanvas */ private void createCircleBitmap(Canvas targetCanvas) { //初始化画笔 Paint paint = new Paint(); paint.setAntiAlias(true); //获取缩放比例 float scale = mPresenter.getScale(mCount); int viewSize = Math.min(mWidth, mHeight); //获取与大圆最上面相切小圆的左上角位置,为了方便计算每个圆具体展示在哪个位置 float[] topPosition = mPresenter.getTopPosition(viewSize / 2, scale); //获取旋转角度 float[] rotation = mPresenter.getRotation(mCount); Matrix matrix = new Matrix(); matrix.postScale(scale, scale); for (int i = 0; i < mCount; i++) { CircleImageViewBean bean = mCircleImageViewBeanList.get(i); //对canvas进行状态保存,防止被变换引起状态的改变 targetCanvas.save(); float[] radianPosition = mPresenter.getTranslatePosition(viewSize / 2, rotation[i], scale, topPosition[0], topPosition[1]); targetCanvas.translate(radianPosition[0], radianPosition[1]); Bitmap scaleBitmap = getScaleBitmap(bean, matrix, viewSize, scale); if (scaleBitmap == null) { return; } int newSize = Math.min(scaleBitmap.getWidth(), scaleBitmap.getHeight()); int center = newSize / 2; //画边缘圈 boolean isDrawBorder = drawCircleBorder(targetCanvas, center, paint); Bitmap circleBitmap = drawCircleBitmap(scaleBitmap, newSize, isDrawBorder); paint.setStyle(Paint.Style.FILL); targetCanvas.drawBitmap(circleBitmap, 0, 0, paint); targetCanvas.restore(); } } /** * 获取缩放后的位图 * @param bean * @param matrix * @param viewSize * @param scale * @return */ private Bitmap getScaleBitmap(CircleImageViewBean bean, Matrix matrix, int viewSize, float scale) { if (bean == null) { return null; } Bitmap newBitmap = null; Bitmap bitmap = bean.getBitmap(); String text = bean.getText(); if (bitmap != null) { int width = bitmap.getWidth(); int height = bitmap.getHeight(); if (width > 0 && height > 0) { //对位图进行缩放 float scaleX = (float) mWidth / width; float scaleY = (float) mHeight / height; Matrix bitmapMatrix = new Matrix(); bitmapMatrix.postScale(scaleX, scaleY); bitmapMatrix.postConcat(matrix); newBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, bitmapMatrix, true); } } else { int size = (int) (viewSize * scale); newBitmap = createRandomMaskBitmap(size, scale, text); } return newBitmap; } /** * * @param newBitmap * @param newSize * @param isDrawBorder * @return */ private Bitmap drawCircleBitmap(Bitmap newBitmap, int newSize, boolean isDrawBorder) { Bitmap bitmap = Bitmap.createBitmap(newSize, newSize, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); Paint paint = new Paint(); paint.setAntiAlias(true); int center = newSize / 2; if (isDrawBorder) { canvas.scale(DEFAULT_SCALE, DEFAULT_SCALE, center, center); } //在矩阵中心画圆,与矩阵的四边相切 canvas.drawCircle(center, center, center, paint); //设置Xfermode为SRC_IN paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); //绘制图片 canvas.drawBitmap(newBitmap, 0, 0, paint); return bitmap; } /** * 创建圆形图像 * * @param targetCanvas * @return */ private Bitmap createCircleBitmapForSRC_IN(Canvas targetCanvas) { //创建一个和图片大小差不多的正方形矩阵 int size = Math.min(mWidth, mHeight); Bitmap newBitmap = null; if (mBitmap != null) { int width = mBitmap.getWidth(); int height = mBitmap.getHeight(); // 对bitmap进行缩放,缩放到指定view的大小 Matrix matrix = new Matrix(); matrix.postScale((float) mWidth / width, (float) mHeight / height); newBitmap = Bitmap.createBitmap(mBitmap, 0, 0, width, height, matrix, true); } else { newBitmap = createRandomMaskBitmap(size, 1.0f, mText); } if (newBitmap == null) { return null; } int center = size / 2; Paint paint = new Paint(); boolean isDrawBorder = drawCircleBorder(targetCanvas, center, paint); Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); if (isDrawBorder) { paint.setColor(Color.WHITE); paint.setStyle(Paint.Style.FILL); canvas.scale(DEFAULT_SCALE, DEFAULT_SCALE, center, center); } //在矩阵中心画圆,与矩阵的四边相切 canvas.drawCircle(center, center, center, paint); //设置Xfermode为SRC_IN paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); //绘制图片 canvas.drawBitmap(newBitmap, 0, 0, paint); return bitmap; } /** * 获取bitmap * * @param drawable * @return */ private Bitmap getBitmapFromDrawable(Drawable drawable) { if (drawable == null) { return null; } if (drawable instanceof BitmapDrawable) { return ((BitmapDrawable) drawable).getBitmap(); } try { Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); drawable.draw(canvas); return bitmap; } catch (OutOfMemoryError e) { return null; } } /** * 绘制边界圆 * * @param canvas * @param size * @param paint * @return */ private boolean drawCircleBorder(Canvas canvas, int size, Paint paint) { if (mStrokeWidth > 0) { paint.setAntiAlias(true); paint.setColor(mStrokeColor); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(mStrokeWidth); canvas.drawCircle(size, size, size - mStrokeWidth, paint); return true; } return false; } /** * 创建随机背景 * * @param size * @return */ private Bitmap createRandomMaskBitmap(int size, float scale, String text) { Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); final Paint paint = new Paint(); paint.setAntiAlias(true);// 抗锯齿 paint.setFilterBitmap(true); paint.setColor(mBackground); int center = size / 2;//获取画布的中心位置 //创建canvas对象,绘制随机背景 Canvas canvas = new Canvas(output); canvas.drawCircle(center, center, center, paint); //绘制随机背景上的文字 setText(canvas, size, paint, scale, text); return output; } /** * 绘制文本 * * @param canvas * @param size * @param paint */ private void setText(Canvas canvas, int size, Paint paint, float scale, String text) { Rect targetRect = new Rect(0, 0, size, size); //设置绘制文本字体的颜色 paint.setColor(Color.WHITE); //设置绘制文本的大小 paint.setTextSize(mTextSize * scale); //获取文本展示的居中位置 Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt(); int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2; paint.setTextAlign(Paint.Align.CENTER); canvas.drawText(text, targetRect.centerX(), baseline, paint); }}
CircleImageViewPresenter.java package com.dylan.circleimageview;/** * Description * author Dylan.zhuang * Date: 16/7/7-下午2:51 */public class CircleImageViewPresenter { /** * 每种头像对应的旋转角度 */ private static final float[][] sRotationArray = { new float[] { 360 }, new float[] { 45, -135 }, new float[] { 120, 0, -120 }, new float[] { 45, 135, -135, -45 }, new float[] { 144, 72, 0, -72, -144 }}; /** * 经过小圆圆心的两条直线ab,和经过大圆圆心的两条直线cd,a和c垂直,b和d垂直(对两个圆的条件不成立) * 公式为360/(n*2),n代表小圆个数,分割策略如下 * 基数圆经过每个圆的圆心画直线,1个圆除外; * 偶数圆经过每个圆的圆心画直线,并在公切线也画直线; * 分割策略提到的直线都经过大圆圆心 */ private static final int[] sAngleArray = {360, 90, 60, 45, 36}; /** * 获取旋转角度 * @param count * @return */ public float[] getRotation(int count) { return count > 0 && count <= sRotationArray.length ? sRotationArray[count - 1] : null; } /** * 获取缩放比例 * @param count * @return */ public float getScale(int count) { int angle = getAngle(count); if (angle == 360) { return 1f; } double cot = getCot(angle); float scale = (float) (1f / (Math.sqrt(1 + Math.pow(cot, 2)) + 1)); return scale; } /** * 获取最上面圆的左上角的值,方便计算每个圆的位置 * @param radius * @param scale * @return */ public float[] getTopPosition(float radius, float scale) { float x = radius * (1 - scale); float y = 0; return new float[] { x, y }; } /** * 获取每个小头像应该平移的距离,找圆心 * @param radius * @param rotation * @param scale * @param topX * @param topY * @return */ public float[] getTranslatePosition(float radius, float rotation, float scale, float topX, float topY) { float smallRadius = radius * (1 - scale); double radian = Math.toRadians(rotation); double cos = Math.cos(radian); double sin = Math.sin(radian); float x = (float) (topX - smallRadius * sin); float y = (float) (topY + smallRadius * (1 - cos)); return new float[] { x, y }; } /** * 获取角度 * @param count * @return */ private int getAngle(int count) { return count > 0 && count <= sAngleArray.length ? sAngleArray[count - 1] : null; } /** * 获取cot值 * @param angle * @return */ private double getCot(int angle) { double radian = Math.toRadians(angle); double sin = Math.sin(radian); double cos = Math.cos(radian); return cos / sin; }}
MainActivity.java package com.dylan.circleimageview;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import java.util.ArrayList;import java.util.List;public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); CircleImageView circleImageView = (CircleImageView) findViewById(R.id.random_icon); circleImageView.drawRandomBackground(); CircleImageView circleImageView1 = (CircleImageView) findViewById(R.id.random_icon1); circleImageView1.drawRandomBackground(); List list2 = new ArrayList<>(); List list3 = new ArrayList<>(); List list4 = new ArrayList<>(); List list5 = new ArrayList<>(); Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test); CircleImageViewBean bean1 = new CircleImageViewBean(bitmap, -1, ""); CircleImageViewBean bean2 = new CircleImageViewBean(null, R.color.colorGreen, "A"); CircleImageViewBean bean3 = new CircleImageViewBean(bitmap, R.color.colorGreen, "A"); CircleImageViewBean bean4 = new CircleImageViewBean(null, R.color.colorAccent, "B"); CircleImageViewBean bean5 = new CircleImageViewBean(bitmap, R.color.colorGreen, "A"); list2.add(bean1); list2.add(bean2); CircleImageView circleImageView2 = (CircleImageView) findViewById(R.id.icon_two); circleImageView2.setImageBitmaps(list2); list3.add(bean3); list3.addAll(list2); CircleImageView circleImageView3 = (CircleImageView) findViewById(R.id.icon_three); circleImageView3.setImageBitmaps(list3); list4.add(bean4); list4.addAll(list3); CircleImageView circleImageView4 = (CircleImageView) findViewById(R.id.icon_four); circleImageView4.setImageBitmaps(list4); list5.add(bean5); list5.addAll(list4); CircleImageView circleImageView5 = (CircleImageView) findViewById(R.id.icon_five); circleImageView5.setImageBitmaps(list5); }}
activity_main.xml <?xml version="1.0" encoding="utf-8"?>
github地址 更多相关文章
- Android(安卓)九宫格解锁Demo--Android(安卓)进阶之路
- Android(安卓)View的绘制之 从源码了解measure的过程。
- android中Bitmap导致的内存溢出
- Android(安卓)贝塞尔曲线实现QQ拖拽清除效果
- Android(安卓)仪表进度条 自定义View
- Android开发-----03-使用Canvas绘制虚线……
- Android(安卓)自定义View(三):重写View实现全新控件
- 【iOS-cocos2d-X 游戏开发之十六】配置你的Cocos2dx项目编译后的
- 乐博Android客户端发布