前言:虽然在上一篇博客中, Android长按图片保存至相册 中我们实现了我们的功能,里面我们自定义了一个叫loaddingview的自定义控件,主要用于显示加载图片的进度。
可是当我们有很多图片,并且每个图片都有一个loaddingview作为加载标识的时候,我们的loaddingview就需要性能优化一下了,因为如果这么多loaddingview同时出现,因为我们是在属性动画中不断更新view的,然后view的更新跟绘制都是在主线程中完成的,想想都会很卡~!!!亦或是想想那种很大的3d游戏,很酷炫的界面,如果还是用普通的view的话,那估计画面太美了,都不敢想象。
那有没有可能在子线程中去更新view跟绘制view呢??? 有的!android早早的就给我们提供了一个叫surfaceview的东东~~ 我也没用过几次,准确的来说没有正式的用过这么一个东西。以前在学校玩过一点点,既然如此,那就让我们来看看这个东西。
一、什么是surfaceview?
surface的一些概念性的东西,小伙伴可以看看老罗的这篇博客(大神已经为我们解释的很清楚了):
http://blog.csdn.net/luoshengyang/article/details/8661317
二、surfaceView和View的区别?:
surfaceView是在一个新起的单独线程中可以重新绘制画面,而View必须在UI的主线程中更新画面。那么在UI的主线程中更新画面 可能会引发问题,比如你更新画面的时间过长,那么你的主UI线程会被你正在画的函数阻塞。那么将无法响应按键,触屏等消息。当使用surfaceView 由于是在新的线程中更新画面所以不会阻塞你的UI主线程。
三、什么时候需要用surfaceview?
当需要快速地更新View的UI,或者当渲染代码阻塞GUI线程的时间过长的时候,SurfaceView就是解决上述问题的最佳选择。 SurfaceView封装了一个Surface对象,而不是Canvas。这一点很重要,因为Surface可以使用后台线程绘制。对于那些资源敏感的 操作,或者那些要求快速更新或者高速帧率的地方,例如,使用3D图形,创建游戏,或者实时预览摄像头,这一点特别有用。
四、surfaceview带来的问题
通过上面的介绍,我们大概了解了下surfaceview,我们知道surfaceview是独立于ui线程完成的绘制工作的,这也说明了需要单独的为它开辟一块空间,需要更多的内存。
好啦!!说了那么多概念性的东西,我自己都累了,下面进入surfaceview的实践:
实现surfaceview的步骤大致为:
package com.leo.camerroll;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
* Created by leo on 17/1/23.
*/
public class testview extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
public testview(Context context) {
this(context,null);
}
public testview(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public testview(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mHolder=getHolder();
mHolder.addCallback(this);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
new Thread(){
@Override
public void run() {
while(true){
Canvas canvas=mHolder.lockCanvas();
///draw something
mHolder.unlockCanvasAndPost(canvas);
}
}
}.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mHolder.removeCallback(this);
}
}
好啦!!我们直接把我们前面博客中写的loaddingview的代码copy过来:
package com.leo.camerroll;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
* Created by leo on 17/1/23.
*/
public class SurLoadingView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
private boolean isRunning = true;
private Thread mDrawTread;
private int DEFAULT_RADIUS = dp2px(15);
private int DEFAULT_REACH_COLOR = 0XFFFFFFFF;
private int DEFAULT_UNREACH_COLOR = 0X88000000;
private int DEFAULT_TEXT_COLOR = 0XFFFFFFFF;
private long ANIM_DURATION = 1000;
private String BASE_TEXT = "00%";
private boolean isStop;
private int mRadius = DEFAULT_RADIUS;
private int mStrokeWidth;
private Paint reachPaint;
private Paint unreachPaint;
private Paint textPaint;
private Paint bgPaint;
private int mStartAngle = 0;
private float mSweepAngle = 360 * 0.382f;
private int progress;
private int max;
public int getProgress() {
return progress;
}
public void setProgress(int progress) {
this.progress = progress;
}
public int getMax() {
return max;
}
public void setMax(int max) {
this.max = max;
}
public SurLoadingView(Context context) {
this(context, null);
}
public SurLoadingView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SurLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
mHolder = getHolder();
mHolder.addCallback(this);
//this.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
reachPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
reachPaint.setStrokeCap(Paint.Cap.ROUND);
reachPaint.setStyle(Paint.Style.STROKE);
unreachPaint = new Paint(reachPaint);
reachPaint.setColor(DEFAULT_REACH_COLOR);
unreachPaint.setColor(DEFAULT_UNREACH_COLOR);
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
textPaint.setColor(DEFAULT_TEXT_COLOR);
textPaint.setFakeBoldText(true);
textPaint.setStyle(Paint.Style.FILL);
bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
bgPaint.setStrokeCap(Paint.Cap.ROUND);
bgPaint.setColor(Color.argb(44, 0, 0, 0));
setMax(100);
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int defWidth = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
int defHeight = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
int expectSize = Math.min(defHeight, defWidth);
if (expectSize <= 0) {
expectSize = mRadius * 2;
} else {
mRadius = expectSize / 2;
}
mStrokeWidth = mRadius / 5;
reachPaint.setStrokeWidth(mStrokeWidth);
unreachPaint.setStrokeWidth(mStrokeWidth);
setMeasuredDimension(expectSize, expectSize);
float textSize = 0;
while (true) {
textSize += 0.1;
textPaint.setTextSize(textSize);
if (textPaint.measureText(BASE_TEXT, 0, BASE_TEXT.length()) >= mRadius) {
break;
}
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mDrawTread = new Thread() {
@Override
public void run() {
while (isRunning) {
synchronized (mHolder) {
Canvas canvas = mHolder.lockCanvas(null);
drawContent(canvas);
if ((mStartAngle+=10) == 360) {
mStartAngle = 0;
}
SystemClock.sleep(10);
}
}
}
};
mDrawTread.start();
}
private void drawContent(Canvas canvas) {
try {
canvas.drawCircle(getWidth() / 2, getWidth() / 2, mRadius - mStrokeWidth, bgPaint);
//draw reach
drawProgressReach(canvas);
//draw progress text
drawProgressText(canvas);
} catch (Exception e) {
} finally {
try {
mHolder.unlockCanvasAndPost(canvas);
}catch (Exception e){
e.printStackTrace();
}
}
}
private void drawProgressText(Canvas canvas) {
String text = String.valueOf((int) (getProgress() * 1.0f / getMax() * 100)) + "%";
int centerX = getWidth() / 2;
int centerY = getWidth() / 2;
int baseX = (int) (centerX - textPaint.measureText(text, 0, text.length()) / 2);
int baseY = (int) (centerY - (textPaint.getFontMetrics().ascent + textPaint.getFontMetrics().descent) / 2);
canvas.drawText(text, baseX, baseY, textPaint);
}
private void drawProgressReach(Canvas canvas) {
canvas.drawArc(new RectF(0 + mStrokeWidth / 2, 0 + mStrokeWidth / 2, mRadius * 2 - mStrokeWidth / 2, mRadius * 2 - mStrokeWidth / 2), mStartAngle, mSweepAngle, false, reachPaint);
//drawonreach
canvas.drawArc(new RectF(0 + mStrokeWidth / 2, 0 + mStrokeWidth / 2, mRadius * 2 - mStrokeWidth / 2, mRadius * 2 - mStrokeWidth / 2), mStartAngle + mSweepAngle, 360 - mSweepAngle, false, unreachPaint);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
isRunning = false;
if (mDrawTread != null) {
mDrawTread = null;
}
}
/**
* @param size
* @return px
*/
private int dp2px(int size) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, getContext().getResources().getDisplayMetrics());
}
}
代码做了点小修改,把前面的属性动画改成了:
mDrawTread = new Thread() {
@Override
public void run() {
while (isRunning) {
synchronized (mHolder) {
Canvas canvas = mHolder.lockCanvas(null);
drawContent(canvas);
if ((mStartAngle+=10) == 360) {
mStartAngle = 0;
}
SystemClock.sleep(10);
}
}
}
};
mDrawTread.start();
好啦!!我们来运行下我们的项目:
效果不对啊,surfaceview的背景咋是这样的呢?
我们尝试把cavans的背景改为透明:
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
结果还是这吊样:
为了去掉surfaceview原有的背景除了把canvas的背景设为透明外,还需要加上:
private void initView() {
mHolder = getHolder();
mHolder.addCallback(this);
//this.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
setZOrderOnTop(true);
getHolder().setFormat(PixelFormat.TRANSLUCENT);
。。。。。
}
我们运行代码:
setZOrderOnTop(true);有什么用呢?
/**
* Control whether the surface view's surface is placed on top of its
* window. Normally it is placed behind the window, to allow it to
* (for the most part) appear to composite with the views in the
* hierarchy. By setting this, you cause it to be placed above the
* window. This means that none of the contents of the window this
* SurfaceView is in will be visible on top of its surface.
*
* <p>Note that this must be set before the surface view's containing
* window is attached to the window manager.
*
* <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
*/
public void setZOrderOnTop(boolean onTop) {
if (onTop) {
mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
// ensures the surface is placed below the IME
mLayout.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
} else {
mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
mLayout.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
}
}
是否让surfaceview的surface在当前window的上面,也就是说是否让我们绘制的内容在我们的window的上面,默认是false的,、。
/**
* Set the desired PixelFormat of the surface. The default is OPAQUE.
* When working with a {@link SurfaceView}, this must be called from the
* same thread running the SurfaceView's window.
*
* @param format A constant from PixelFormat.
*
* @see android.graphics.PixelFormat
*/
public void setFormat(int format);
surface的像素格式,默认是OPAQUE(不透明的),我们需要改为透明色。
好了,我们的loaddingview完美的实现啦!! 是不是soeasy呢?
最后附上demo的Git链接:
https://github.com/913453448/CamerRoll