SurfaceView简介与应用

SurfaceView 在 Android 零碎中,是一种非凡的视图。它领有独立的绘图外表,即它不与宿主窗口共享同一个绘图外表。

因为领有独立的绘图外表,因而 SurfaceView 的 UI 就能够在一个独立的线程中进行绘制,又因为不会占用主线程资源, 使用 SurfaceView 能够实现简单而高效的 UI,另一方面又不会导致用户输出得不到及时响应。 比拟适宜利用在视频播放,图片浏览,对画面要求高的游戏下面。

SurfaceView相当于是在屏幕外面,而屏幕给它开了一个洞。给它设置背景色就能把它盖住。

应用 SurfaceView 的起因之一,是可能在子线程中更新图像。加重 UI 线程的压力。
所有 SurfaceView 和 SurfaceHolder.Callback 的办法都应该在 UI 线程里调用,一般来说就是应用程序主线程。 渲染线程所要拜访的各种变量应该作同步解决。要确保绘图线程仅在 surface 可用期间进行绘图。
SurfaceView 次要由 SurfaceHolder 来管制,holder 相当于一个控制器。

外围要点
  • View:必须在UI的主线程中更新画面,用于被动更新画面。
  • SurfaceView:UI线程和子线程中都能够。在一个新启动的线程中从新绘制画面,被动更新画面。

    java.lang.Objectandroid.view.Viewandroid.view.SurfaceView

应用办法

创立一个类继承SurfaceView并实现SurfaceHolder.Callback接口。

public class MySView extends SurfaceView implements SurfaceHolder.Callback {    .........}
获取SurfaceHolder

在构造函数中获取 SurfaceHolder。并增加回调。

public MySView(Context context, AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);    holder = getHolder();    holder.addCallback(this);    // ......}
复写办法
@Overridepublic void surfaceCreated(SurfaceHolder holder) {    Log.d(TAG, "surfaceCreated");    drawThread = new DrawThread(holder);// 创立一个绘图线程    drawThread.start();}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {    Log.d(TAG, "surfaceChanged");}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {    drawThread.closeThread();// 销毁线程    Log.d(TAG, "surfaceDestroyed");}

当 Activity 不可见时,SurfaceView 会调用 surfaceDestroyed 办法。此时就要销毁绘图线程。

绘图子线程

SurfaceView 中绘图操作是在子线程中进行的。失常来说子线程不能操作 UI。然而咱们能够应用 SurfaceHolder 提供的办法锁定画布,绘图实现后开释并更新画布。 上面的线程提供了进行、复原和完结等性能。

class DrawThread extends Thread {    private SurfaceHolder mmHolder;    private boolean mmRunning;    private boolean mmIsPause;    public DrawThread(SurfaceHolder holder) {        this.mmHolder = holder;        mmRunning = true;    }    @Override    public void run() {        while (mmRunning && !isInterrupted()) {            if (!mmIsPause) {                Canvas canvas = null;                try {                    synchronized (mmHolder) {                        canvas = holder.lockCanvas();        // 锁定画布,取得返回的画布对象Canvas                        canvas.drawColor(bgSurfaceViewColor);// 设置画布背景色彩                        // 绘图操作......                    }                } catch (Exception e) {                    e.printStackTrace();                } finally {                    if (canvas != null) {                        mmHolder.unlockCanvasAndPost(canvas);// 开释画布,并提交扭转。                    }                    pauseThread();                }            } else {                onThreadWait();            }        }    }    public synchronized void pauseThread() {        mmIsPause = true;    }    /** * 线程期待,不提供给内部调用 */    private void onThreadWait() {        try {            synchronized (this) {                this.wait();            }        } catch (Exception e) {            e.printStackTrace();        }    }    public synchronized void resumeThread() {        mmIsPause = false;        this.notify();    }    public synchronized void closeThread() {        try {            mmRunning = false;            notify();            interrupt();        } catch (Exception e) {            e.printStackTrace();        }    }}
layout中应用

切记不可在 layout 中设置 background,色彩会间接把 SurfaceView 挡住。

<com.rustfisher.fisherandroidchart.MySView    android:id="@+id/mySurfaceView"    android:layout_width="match_parent"    android:layout_height="230dp" />

Android 主动缩放上上限的折线图

一条折线,依据最大最小值主动缩放上上限。

  • 继承View
  • 数据应用FloatBuffer存储
  • 可扭转显示窗口的大小
  • 可指定坐标轴,折线和字体色彩

    import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Rect;import android.util.AttributeSet;import android.util.Log;import android.util.TypedValue;import android.view.View;import java.nio.FloatBuffer;public class AutoLineChart extends View {  private static final String TAG = "rustApp" + AutoLineChart.class.getSimpleName();  private float yMax = 1.6f;  private float yMin = -1.0f;  float yAxisZoomLimitMax = 1.6f;  float yAxisZoomLimitMin = -1.0f; // 放大y轴的极限值  // Y轴主动缩放时的增减间隔  float axisYPerStep = 0.1f;  // 图表线条在view顶部留出的间距  float viewYStart = 2;  float axisTextSize = 10;  private int onShowPointsCount = 500;  // 以后显示的数据个数  int onShowMinPoints = 100;            // 至多要显示的数据个数  private int maxPoint = 9000;          // 数据存储最大个数  // 坐标轴线条宽度  float axisLineWid = 1f;  int dataLineWid = 4;  // 数据线色彩  private int dataColor = Color.parseColor("#eaffe9");  // 图表中的背景线条色彩  private int mainBgLineColor = Color.parseColor("#535353");  // 坐标轴色彩  private int axisColor = Color.WHITE;  // 坐标值字体色彩  private int axisTextColor = Color.WHITE;  // 背景色  private int viewBgColor = Color.parseColor("#222222");  Rect rectText = new Rect();  private float xStep = 1.0f;  private float viewWidth;  private float viewHeight;  private float botLeftXOnView = 0; // 图表左下点在view中的x坐标  private float botLeftYOnView = 0;  private float originYToBottom = 20; // 图表原点间隔view底部的间隔  private FloatBuffer dataBuffer;  private Paint bgPaint;  private Paint linePaint;  public AutoLineChart(Context context) {      this(context, null);  }  public AutoLineChart(Context context, AttributeSet attrs) {      this(context, attrs, 0);  }  public AutoLineChart(Context context, AttributeSet attrs, int defStyleAttr) {      super(context, attrs, defStyleAttr);      init(context);  }  public int getMaxPoint() {      return maxPoint;  }  public void setOnShowPointsCount(int onShowPointsCount) {      this.onShowPointsCount = onShowPointsCount;  }  public int getOnShowPointsCount() {      return onShowPointsCount;  }  public int getOnShowMinPoints() {      return onShowMinPoints;  }  public void addData(float data) {      dataBuffer.put(data);      if (dataBuffer.position() > (dataBuffer.capacity() * 2 / 3)) {          float[] bufferArr = dataBuffer.array();          System.arraycopy(bufferArr, dataBuffer.position() - maxPoint, bufferArr, 0, maxPoint);          dataBuffer.position(maxPoint);//            Log.d(TAG, "把以后数据挪动到buffer起始地位 " + dataBuffer);      }      invalidate();  }  private void init(Context context) {      dataBuffer = FloatBuffer.allocate(3 * maxPoint); // 调配3倍的空间      bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);      linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);      bgPaint.setStrokeWidth(axisLineWid);      bgPaint.setStyle(Paint.Style.STROKE);      bgPaint.setColor(mainBgLineColor);      linePaint.setStrokeWidth(dataLineWid);      linePaint.setStyle(Paint.Style.STROKE);      linePaint.setColor(dataColor);      botLeftXOnView = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 32, context.getResources().getDisplayMetrics());      originYToBottom = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, context.getResources().getDisplayMetrics());      viewYStart = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, context.getResources().getDisplayMetrics());      axisLineWid = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, context.getResources().getDisplayMetrics());      axisTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, context.getResources().getDisplayMetrics());  }  @Override  protected void onSizeChanged(int w, int h, int oldw, int oldh) {      super.onSizeChanged(w, h, oldw, oldh);      viewWidth = getWidth();      viewHeight = getHeight();      botLeftYOnView = viewHeight - originYToBottom;  }  @Override  protected void onDraw(Canvas canvas) {      super.onDraw(canvas);      canvas.drawColor(viewBgColor);      xStep = (viewWidth - botLeftXOnView) / (onShowPointsCount - 1);      float maxData = 0.1f;      float minData = 0;      int dataStartIndexInBuffer = 0; // 数据在buffer中的起始下标      if (dataBuffer.position() > onShowPointsCount) {          dataStartIndexInBuffer = dataBuffer.position() - onShowPointsCount;      }      float[] bufferArr = dataBuffer.array();      for (int i = dataStartIndexInBuffer; i < dataBuffer.position(); i++) {          if (bufferArr[i] < minData) {              minData = bufferArr[i];          } else if (bufferArr[i] > maxData) {              maxData = bufferArr[i];          }      }      zoomYAxis(maxData, minData);      drawBgLines(canvas);      drawWave(canvas, dataStartIndexInBuffer);  }  // 缩放Y轴  private void zoomYAxis(float maxData, float minData) {      if (maxData < yAxisZoomLimitMax) {          yMax = yAxisZoomLimitMax;      } else if (maxData < yMax) {          while (maxData < yMax) {              yMax -= axisYPerStep;          }          yMax += axisYPerStep;      } else if (maxData > yMax) {          while (maxData > yMax) {              yMax += axisYPerStep;          }      }      if (minData > yAxisZoomLimitMin) {          yMin = yAxisZoomLimitMin;      } else if (minData > yMin) {          while (minData > yMin) {              yMin += axisYPerStep;          }          yMin -= axisYPerStep;      } else if (minData < yMin) {          yMin -= axisYPerStep;      }  }  private void drawBgLines(Canvas canvas) {      // 画坐标轴      bgPaint.setStyle(Paint.Style.FILL);      bgPaint.setStrokeWidth(axisLineWid);      bgPaint.setTextSize(axisTextSize);      bgPaint.setTextAlign(Paint.Align.RIGHT);      for (float y = 0; y <= yMax; y += 0.5) {          drawYAxis(canvas, y);      }      for (float y = 0; y >= yMin; y -= 0.5) {          drawYAxis(canvas, y);      }      bgPaint.setColor(axisColor);      canvas.drawLine(botLeftXOnView, viewYStart / 2, botLeftXOnView, botLeftYOnView + viewYStart / 2, bgPaint);//        canvas.drawLine(botLeftXOnView, botLeftYOnView, viewWidth, botLeftYOnView, bgPaint); // x轴  }  private void drawYAxis(Canvas canvas, float axisYValue) {      final float yDataRange = yMax - yMin;      final float yAxisRangeOnView = botLeftYOnView - viewYStart;      float aY = botLeftYOnView - (axisYValue - yMin) / yDataRange * yAxisRangeOnView;      bgPaint.setColor(axisColor);      canvas.drawLine(botLeftXOnView - 20, aY, botLeftXOnView, aY, bgPaint);      String axisText = String.valueOf(axisYValue);      bgPaint.getTextBounds(axisText, 0, axisText.length(), rectText); // 获取文本的宽高      canvas.drawText(axisText, botLeftXOnView - rectText.width() / 2, aY + rectText.height() / 2, bgPaint);      bgPaint.setColor(mainBgLineColor);      canvas.drawLine(botLeftXOnView, aY, viewWidth, aY, bgPaint);  }  private void drawWave(Canvas canvas, int dataStartIndexInBuffer) {      final float yDataRange = yMax - yMin;      final float yAxisRangeOnView = botLeftYOnView - viewYStart;      final float yDataStep = yAxisRangeOnView / yDataRange;      float[] dataArr = dataBuffer.array();      for (int i = dataStartIndexInBuffer; i < dataBuffer.position() - 1; i++) {          canvas.drawLine(botLeftXOnView + (i - dataStartIndexInBuffer) * xStep, getYL(dataArr[i], yDataStep),                  botLeftXOnView + (i - dataStartIndexInBuffer + 1) * xStep, getYL(dataArr[i + 1], yDataStep),                  linePaint);      }  }  private float getYL(final float yData, float yDataStep) {      return botLeftYOnView - (yData - yMin) * yDataStep;  }}

自定义 View 面试题

1. 讲下 View 的绘制流程?

参考答复:

View 的工作流程次要是指 measure、layout、draw 这三大流程,即测量、布局和绘制,

  • measure 确定 View 的 测量宽/高;
  • layout 确定 View 的最终宽/高和四个顶点的地位;
  • draw 则将 View 绘制到屏幕上;

View的绘制过程遵循如下几步:

  • 绘制背景 background.draw(canvas)
  • 绘制本人(onDraw)
  • 绘制 children(dispatchDraw)
  • 绘制装璜(onDrawScollBars)

2. MotionEvent是什么?蕴含几种事件?什么条件下会产生?

参考答复:

MotionEvent 是手指接触屏幕后所产生的一系列事件。典型的事件类型有如下:

  • ACTION_DOWN:手指刚接触屏幕
  • ACTION_MOVE:手指在屏幕上挪动
  • ACTION_UP:手指从屏幕上松开的一瞬间
  • ACTION_CANCELL:手指放弃按下操作,并从以后控件转移到外层控件时触发

失常状况下,一次手指触摸屏幕的行为会触发一系列点击事件,思考如下几种状况:

  • 点击屏幕后松开,事件序列:DOWN→UP
  • 点击屏幕滑动一会再松开,事件序列为DOWN→MOVE→.....→MOVE→UP
3. 形容一下 View 事件传递散发机制?

参考答复:

View 事件散发实质就是对 MotionEvent 事件散发的过程。即当一个 MotionEvent 产生后,零碎将这个点击事件传递到一个具体的 View 上

点击事件的传递程序:Activity(Window)→ViewGroup→ View

事件散发过程由三个办法共同完成:

  • dispatchTouchEvent:用来进行事件的散发。如果事件可能传递给以后View,那么此办法肯定会被调用,返回后果受以后View的onTouchEvent和上级View的dispatchTouchEvent办法的影响,示意是否耗费以后事件
  • onInterceptTouchEvent:在上述办法外部调用,对事件进行拦挡。该办法只在ViewGroup中有,View(不蕴含 ViewGroup)是没有的。一旦拦挡,则执行ViewGroup的onTouchEvent,在ViewGroup中处理事件,而不接着分发给View。且只调用一次,返回后果示意是否拦挡以后事件
  • onTouchEvent: 在dispatchTouchEvent办法中调用,用来解决点击事件,返回后果示意是否耗费以后事件
4. 如何解决 View 的事件抵触 ? 举个开发中遇到的例子 ?

参考答复:

常见开发中事件抵触的有 ScrollView 与 RecyclerView 的滑动抵触、RecyclerView 内嵌同时滑动同一方向

滑动抵触的解决规定:

  • 对于因为内部滑动和外部滑动方向不统一导致的滑动抵触,能够依据滑动的方向判断谁来拦挡事件。
  • 对于因为内部滑动方向和外部滑动方向统一导致的滑动抵触,能够依据业务需要,规定何时让内部View拦挡事件,何时由外部View拦挡事件。
  • 对于下面两种状况的嵌套,绝对简单,可同样依据需要在业务上找到突破点。

滑动抵触的实现办法:

  • 内部拦截法:指导击事件都先通过父容器的拦挡解决,如果父容器须要此事件就拦挡,否则就不拦挡。具体方法:须要重写父容器的onInterceptTouchEvent办法,在外部做出相应的拦挡。
  • 外部拦截法:指父容器不拦挡任何事件,而将所有的事件都传递给子容器,如果子容器须要此事件就间接耗费,否则就交由父容器进行解决。具体方法:须要配合requestDisallowInterceptTouchEvent办法。
5. scrollTo() 和 scollBy() 的区别?

参考答复:

scollBy 外部调用了 scrollTo,它是基于以后地位的绝对滑动;而 scrollTo 是相对滑动,因而如果应用雷同输出参数屡次调用 scrollTo 办法,因为 View 的初始地位是不变的,所以只会呈现一次View滚动的成果
两者都只能对 View 内容的滑动,而非使 View 自身滑动。能够应用 Scroller 有适度滑动的成果

6. Scroller 是怎么实现 View 的弹性滑动?

参考答复:

  • 在 MotionEvent.ACTION_UP 事件触发时调用 startScroll() 办法,该办法并没有进行理论的滑动操作,而是记录滑动相干量(滑动间隔、滑动工夫)
  • 接着调用 invalidate/postInvalidate() 办法,申请 View 重绘,导致 View.draw 办法被执行
  • 当 View 重绘后会在 draw 办法中调用 computeScroll 办法,而 computeScroll 又会去向Scroller 获取以后的 scrollX 和 scrollY;而后通过 scrollTo 办法实现滑动;接着又调用postInvalidate 办法来进行第二次重绘,和之前流程一样,如此重复导致 View 一直进行小幅度的滑动,而屡次的小幅度滑动就组成了弹性滑动,直到整个滑动过成完结.

Android 零根底入门教程视频参考