Android Programming: Pushing the Limits -- Chapter 5: Android User Interface Operations

多屏幕
自定义View

多屏幕

@、Android 4.2 开始支持多屏幕。

@、举例:

public class SecondDisplayDemo extends Activity {

    private Presentation mPresentation;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.device_screen);
    }

    @Override
    protected void onResume() {
        super.onResume();
        setupSecondDisplay();
    }

    @Override
    protected void onPause() {
        super.onPause();
        if(mPresentation != null){
            mPresentation.cancel();
        }
    }

    private void setupSecondDisplay(){
        DisplayManager displayManager = (DisplayManager)
                getSystemService(Context.DISPLAY_SERVICE);
        Display defaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
        Display[] presentationDisplays = displayManager
                .getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
        if(presentationDisplays.length > 0){
            for(Display presentationDisplay : presentationDisplays){
                if(presentationDisplay.getDisplayId() != defaultDisplay.getDisplayId()){
                    Presentation presentation =
                            new MyPresentation(this, presentationDisplay);
                    presentation.show();
                    mPresentation = presentation;
                    return;
                }
            }
        }
        Toast.makeText(this, "No second display found!", Toast.LENGTH_SHORT).show();
    }

    private class MyPresentation extends Presentation{
        public MyPresentation(Context context, Display display){
            super(context, display);
            // The View for the second screen
            setContentView(R.layout.second_screen);
        }
    }
}
SecondDisplayDemo.java
<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:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".SecondDisplayDemo">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:textSize="32sp"
        android:text="@string/first_screen" />

</RelativeLayout>
device_screen.xml
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:paddingLeft="@dimen/activity_horizontal_margin"
              android:paddingRight="@dimen/activity_horizontal_margin"
              android:paddingTop="@dimen/activity_vertical_margin"
              android:paddingBottom="@dimen/activity_vertical_margin"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
    <TextView android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:layout_gravity="center"
              android:gravity="center"
              android:text="@string/second_screen_content"
              android:textSize="32sp" />

</LinearLayout>
second_screen.xml

自定义View:

@、在onAttachedToWindow中加载资源,初始化数据,但最好与大小、位置无关的数据,因为onAttachedToWindow可能在onMeasure之前或之后被调用,这时候可能还不知道view的height和width。

@、View的绘制有两步:a measure pass and a layout pass。

https://developer.android.com/guide/topics/ui/how-android-draws.html

1、  通过measure(int, int)方法确定个组件的height和width,此方法调用可能不止一次,直到所有组件都确定好。此方法会调用onMeasure()。此方法不可被子类覆盖,子类应该重写的是onMeasure()。

2、  layout(int, int, int, int)通过上一步获取的height和width布局子控件。子类不要覆盖此方法而是onLayout方法。在onLayout方法中可调用子控件的layout方法。

@、onLayout处理跟大小、位置相关的数据。

@、onDraw只专注处理绘制工作,而不要有繁重的计算工作。

@、onTouchEvent处理触摸事件。

@、MotionEvent

1、   Each complete gesture is represented by a sequence of motion events with actions that describe pointer state transitions and movements. A gesture starts with a motion event with ACTION_DOWN that provides the location of the first pointer down. As each additional pointer that goes down or up, the framework will generate a motion event with ACTION_POINTER_DOWN or ACTION_POINTER_UP accordingly. Pointer movements are described by motion events with ACTION_MOVE. Finally, a gesture end either when the final pointer goes up as represented by a motion event with ACTION_UP or when gesture is canceled with ACTION_CANCEL.

2、  getActionIndex:获取pointer的下标,简单理解在MotionEvent中包含一个pointer的数组,当发生ACTION_UP或ACTION_POINTER_UP事件时,相当于从数组中删除一个数据,这样pointer的下标就有可能变化。如果是新的pointer,则新pointer的下标为最小空闲下标;如果是原有的pointer,则此pointer的新下标为原下标减去它前面空闲下标的数量。

3、  getPointerId:获取pointer的标识符,标识符不会改变,而下标是会改变的。

4、  findPointerIndex:根据标识符找pointer下标,如果pointer已经失效了则返回-1。

5、  ACTION_DOWN, ACTION_UP, ACTION_MOVE, ACTION_CANCEL, ACTION_OUTSIDE, ACTION_POINTER_DOWN, ACTION_POINTER_UP.

@、PointerCoords:存放pointer coordinates数据。(这些数据可以通过MotionEvent类获取吧,那这个类主要作用是什么?传递时少一些数据吗?)

@、通过Canvas的rotate方法实现旋转。

@、多点触摸实例:

例一:PianoKeyBoard

public class PianoKeyboard extends View {
    public static final String LOG_TAG = "PianoKeyboard";
    public static final int MAX_FINGERS = 5;
    public static final int WHITE_KEYS_COUNT = 7;
    public static final int BLACK_KEYS_COUNT = 5;
    public static final float BLACK_TO_WHITE_WIDTH_RATIO = 0.625f;
    public static final float BLACK_TO_WHITE_HEIGHT_RATIO = 0.54f;
    private Paint mWhiteKeyPaint, mBlackKeyPaint, mBlackKeyHitPaint, mWhiteKeyHitPaint;
    // Support up to five fingers
    private Point[] mFingerPoints = new Point[MAX_FINGERS];
    private int[] mFingerTones = new int[MAX_FINGERS];
    private SoundPool mSoundPool;
    private SparseIntArray mToneToIndexMap = new SparseIntArray();
    private Paint mCKeyPaint, mCSharpKeyPaint, mDKeyPaint,
            mDSharpKeyPaint, mEKeyPaint, mFKeyPaint,
            mFSharpKeyPaint, mGKeyPaint, mGSharpKeyPaint,
            mAKeyPaint, mASharpKeyPaint, mBKeyPaint;
    private Rect mCKey = new Rect(), mCSharpKey = new Rect(),
            mDKey = new Rect(), mDSharpKey = new Rect(),
            mEKey = new Rect(), mFKey = new Rect(),
            mFSharpKey = new Rect(), mGKey = new Rect(),
            mGSharpKey = new Rect(), mAKey = new Rect(),
            mASharpKey = new Rect(), mBKey = new Rect();
    private MotionEvent.PointerCoords mPointerCoords;

    public PianoKeyboard(Context context) {
        super(context);
    }

    public PianoKeyboard(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public PianoKeyboard(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onAttachedToWindow() {
        Log.d(LOG_TAG, "In onAttachedToWindow");
        super.onAttachedToWindow();
        mPointerCoords = new MotionEvent.PointerCoords();
        Arrays.fill(mFingerPoints, null);
        Arrays.fill(mFingerTones, -1);
        loadKeySamples(getContext());
        setupPaints();
    }

    @Override

    protected void onDetachedFromWindow() {
        Log.d(LOG_TAG, "In onDetachedFromWindow");
        super.onDetachedFromWindow();
        releaseKeySamples();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.d(LOG_TAG, "In onMeasure");
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        Log.d(LOG_TAG, "In onLayout");
        super.onLayout(changed, left, top, right, bottom);
        int width = getWidth();
        int height = getHeight();
        int whiteKeyWidth = width / WHITE_KEYS_COUNT;
        int blackKeyWidth = (int) (whiteKeyWidth * BLACK_TO_WHITE_WIDTH_RATIO);
        int blackKeyHeight = (int) (height * BLACK_TO_WHITE_HEIGHT_RATIO);
        mCKey.set(0, 0, whiteKeyWidth, height);
        mCSharpKey.set(whiteKeyWidth - (blackKeyWidth / 2), 0,
                whiteKeyWidth + (blackKeyWidth / 2), blackKeyHeight);
        mDKey.set(whiteKeyWidth, 0, 2 * whiteKeyWidth, height);
        mDSharpKey.set(2 * whiteKeyWidth - (blackKeyWidth / 2), 0,
                2 * whiteKeyWidth + (blackKeyWidth / 2), blackKeyHeight);
        mEKey.set(2 * whiteKeyWidth, 0, 3 * whiteKeyWidth, height);
        mFKey.set(3 * whiteKeyWidth, 0, 4 * whiteKeyWidth, height);
        mFSharpKey.set(4 * whiteKeyWidth - (blackKeyWidth / 2), 0,
                4 * whiteKeyWidth + (blackKeyWidth / 2), blackKeyHeight);
        mGKey.set(4 * whiteKeyWidth, 0, 5 * whiteKeyWidth, height);
        mGSharpKey.set(5 * whiteKeyWidth - (blackKeyWidth / 2), 0,
                5 * whiteKeyWidth + (blackKeyWidth / 2), blackKeyHeight);
        mAKey.set(5 * whiteKeyWidth, 0, 6 * whiteKeyWidth, height);
        mASharpKey.set(6 * whiteKeyWidth - (blackKeyWidth / 2), 0,
                6 * whiteKeyWidth + (blackKeyWidth / 2), blackKeyHeight);
        mBKey.set(6 * whiteKeyWidth, 0, 7 * whiteKeyWidth, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        Log.d(LOG_TAG, "In onDraw");
        super.onDraw(canvas);

        canvas.drawRect(mCKey, mCKeyPaint);
        canvas.drawRect(mDKey, mDKeyPaint);
        canvas.drawRect(mEKey, mEKeyPaint);
        canvas.drawRect(mFKey, mFKeyPaint);
        canvas.drawRect(mGKey, mGKeyPaint);
        canvas.drawRect(mAKey, mAKeyPaint);
        canvas.drawRect(mBKey, mBKeyPaint);

        canvas.drawRect(mCSharpKey, mCSharpKeyPaint);
        canvas.drawRect(mDSharpKey, mDSharpKeyPaint);
        canvas.drawRect(mFSharpKey, mFSharpKeyPaint);
        canvas.drawRect(mGSharpKey, mGSharpKeyPaint);
        canvas.drawRect(mASharpKey, mASharpKeyPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(LOG_TAG, "In onTouchEvent");
        int pointerCount = event.getPointerCount();
        Log.d(LOG_TAG, "In onTouchEvent pointerCount = " + pointerCount);
        int cappedPointerCount = pointerCount > MAX_FINGERS ? MAX_FINGERS : pointerCount;
        Log.d(LOG_TAG, "In onTouchEvent cappedPointerCount = " + cappedPointerCount);
        int actionIndex = event.getActionIndex();
        Log.d(LOG_TAG, "In onTouchEvent actionIndex = " + actionIndex);
        int action = event.getActionMasked();
        Log.d(LOG_TAG, "In onTouchEvent action = " + action);
        int id = event.getPointerId(actionIndex);
        Log.d(LOG_TAG, "In onTouchEvent id = " + id);

        if ((action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) && id < MAX_FINGERS) {
            mFingerPoints[id] = new Point((int) event.getX(actionIndex), (int) event.getY(actionIndex));
        } else if ((action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_UP) && id < MAX_FINGERS) {
            mFingerPoints[id] = null;
            invalidateKey(mFingerTones[id]);
            mFingerTones[id] = -1;
        }

        for (int i = 0; i < cappedPointerCount; i++) {
            int index = event.findPointerIndex(i);
            if (mFingerPoints[i] != null && index != -1) {
                mFingerPoints[i].set((int) event.getX(index), (int) event.getY(index));
                int tone = getToneForPoint(mFingerPoints[i]);
                invalidateKey(1);
                if (tone != mFingerTones[i] && tone != -1) {
                    invalidateKey(mFingerTones[i]);
                    mFingerTones[i] = tone;
                    invalidateKey(mFingerTones[i]);
                    if (!isKeyDown(i)) {
                        int poolIndex = mToneToIndexMap.get(mFingerTones[i]);
                        event.getPointerCoords(index, mPointerCoords);
                        float volume = mPointerCoords.getAxisValue(MotionEvent.AXIS_PRESSURE);
                        volume = volume > 1f ? 1f : volume;
                        mSoundPool.play(poolIndex, volume, volume, 0, 0, 1f);
                    }
                }
            }
        }

        updatePaints();

        return true;
    }

    private void setupPaints() {
        mWhiteKeyPaint = new Paint();
        mWhiteKeyPaint.setStyle(Paint.Style.STROKE);
        mWhiteKeyPaint.setColor(Color.BLACK);
        mWhiteKeyPaint.setStrokeWidth(3);
        mWhiteKeyPaint.setAntiAlias(true);
        mCKeyPaint = mWhiteKeyPaint;
        mDKeyPaint = mWhiteKeyPaint;
        mEKeyPaint = mWhiteKeyPaint;
        mFKeyPaint = mWhiteKeyPaint;
        mGKeyPaint = mWhiteKeyPaint;
        mAKeyPaint = mWhiteKeyPaint;
        mBKeyPaint = mWhiteKeyPaint;

        mWhiteKeyHitPaint = new Paint(mWhiteKeyPaint);
        mWhiteKeyHitPaint.setColor(Color.LTGRAY);
        mWhiteKeyHitPaint.setStyle(Paint.Style.FILL_AND_STROKE);

        mBlackKeyPaint = new Paint();
        mBlackKeyPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mBlackKeyPaint.setColor(Color.BLACK);
        mBlackKeyPaint.setAntiAlias(true);
        mCSharpKeyPaint = mBlackKeyPaint;
        mDSharpKeyPaint = mBlackKeyPaint;
        mFSharpKeyPaint = mBlackKeyPaint;
        mGSharpKeyPaint = mBlackKeyPaint;
        mASharpKeyPaint = mBlackKeyPaint;

        mBlackKeyHitPaint = new Paint(mBlackKeyPaint);
        mBlackKeyHitPaint.setColor(Color.DKGRAY);
    }

    private void loadKeySamples(Context context) {
        mSoundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
//        mToneToIndexMap.put(R.raw.c, mSoundPool.load(context, R.raw.c, 1));
//        mToneToIndexMap.put(R.raw.c_sharp, mSoundPool.load(context, R.raw.c_sharp, 1));
//        mToneToIndexMap.put(R.raw.d, mSoundPool.load(context, R.raw.d, 1));
//        mToneToIndexMap.put(R.raw.d_sharp, mSoundPool.load(context, R.raw.d_sharp, 1));
//        mToneToIndexMap.put(R.raw.e, mSoundPool.load(context, R.raw.e, 1));
//        mToneToIndexMap.put(R.raw.f, mSoundPool.load(context, R.raw.f, 1));
//        mToneToIndexMap.put(R.raw.f_sharp, mSoundPool.load(context, R.raw.f_sharp, 1));
//        mToneToIndexMap.put(R.raw.g, mSoundPool.load(context, R.raw.g, 1));
//        mToneToIndexMap.put(R.raw.g_sharp, mSoundPool.load(context, R.raw.g_sharp, 1));
//        mToneToIndexMap.put(R.raw.a, mSoundPool.load(context, R.raw.a, 1));
//        mToneToIndexMap.put(R.raw.a_sharp, mSoundPool.load(context, R.raw.a_sharp, 1));
//        mToneToIndexMap.put(R.raw.b, mSoundPool.load(context, R.raw.b, 1));
    }

    public void releaseKeySamples() {
        mToneToIndexMap.clear();
        mSoundPool.release();
    }

    private boolean isKeyDown(int finger) {
        int key = getToneForPoint(mFingerPoints[finger]);

        for (int i = 0; i < mFingerPoints.length; i++) {
            if (i != finger) {
                Point fingerPoint = mFingerPoints[i];
                if (fingerPoint != null) {
                    int otherKey = getToneForPoint(fingerPoint);
                    if (otherKey == key) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    private void invalidateKey(int tone) {
        invalidate(mCKey);
        switch (tone) {
//            case R.raw.c:
//                invalidate(mCKey);
//                break;
//            case R.raw.c_sharp:
//                invalidate(mCSharpKey);
//                break;
//            case R.raw.d:
//                invalidate(mDKey);
//                break;
//            case R.raw.d_sharp:
//                invalidate(mDSharpKey);
//                break;
//            case R.raw.e:
//                invalidate(mEKey);
//                break;
//            case R.raw.f:
//                invalidate(mFKey);
//                break;
//            case R.raw.f_sharp:
//                invalidate(mFSharpKey);
//                break;
//            case R.raw.g:
//                invalidate(mGKey);
//                break;
//            case R.raw.g_sharp:
//                invalidate(mGSharpKey);
//                break;
//            case R.raw.a:
//                invalidate(mAKey);
//                break;
//            case R.raw.a_sharp:
//                invalidate(mASharpKey);
//                break;
//            case R.raw.b:
//                invalidate(mBKey);
//                break;
        }
    }

    private void updatePaints() {
        mCKeyPaint = mWhiteKeyPaint;
        mDKeyPaint = mWhiteKeyPaint;
        mEKeyPaint = mWhiteKeyPaint;
        mFKeyPaint = mWhiteKeyPaint;
        mGKeyPaint = mWhiteKeyPaint;
        mAKeyPaint = mWhiteKeyPaint;
        mBKeyPaint = mWhiteKeyPaint;
        mCSharpKeyPaint = mBlackKeyPaint;
        mDSharpKeyPaint = mBlackKeyPaint;
        mFSharpKeyPaint = mBlackKeyPaint;
        mGSharpKeyPaint = mBlackKeyPaint;
        mASharpKeyPaint = mBlackKeyPaint;

        for (Point fingerPoint : mFingerPoints) {
            if (fingerPoint != null) {
                if (mCSharpKey.contains(fingerPoint.x, fingerPoint.y)) {
                    mCSharpKeyPaint = mBlackKeyHitPaint;
                } else if (mDSharpKey.contains(fingerPoint.x, fingerPoint.y)) {
                    mDSharpKeyPaint = mBlackKeyHitPaint;
                } else if (mFSharpKey.contains(fingerPoint.x, fingerPoint.y)) {
                    mFSharpKeyPaint = mBlackKeyHitPaint;
                } else if (mGSharpKey.contains(fingerPoint.x, fingerPoint.y)) {
                    mGSharpKeyPaint = mBlackKeyHitPaint;
                } else if (mASharpKey.contains(fingerPoint.x, fingerPoint.y)) {
                    mASharpKeyPaint = mBlackKeyHitPaint;
                } else if (mCKey.contains(fingerPoint.x, fingerPoint.y)) {
                    mCKeyPaint = mWhiteKeyHitPaint;
                } else if (mDKey.contains(fingerPoint.x, fingerPoint.y)) {
                    mDKeyPaint = mWhiteKeyHitPaint;
                } else if (mEKey.contains(fingerPoint.x, fingerPoint.y)) {
                    mEKeyPaint = mWhiteKeyHitPaint;
                } else if (mFKey.contains(fingerPoint.x, fingerPoint.y)) {
                    mFKeyPaint = mWhiteKeyHitPaint;
                } else if (mGKey.contains(fingerPoint.x, fingerPoint.y)) {
                    mGKeyPaint = mWhiteKeyHitPaint;
                } else if (mAKey.contains(fingerPoint.x, fingerPoint.y)) {
                    mAKeyPaint = mWhiteKeyHitPaint;
                } else if (mBKey.contains(fingerPoint.x, fingerPoint.y)) {
                    mBKeyPaint = mWhiteKeyHitPaint;
                }
            }
        }
    }

    private int getToneForPoint(Point point) {
//        if (mCSharpKey.contains(point.x, point.y))
//            return R.raw.c_sharp;
//        if (mDSharpKey.contains(point.x, point.y))
//            return R.raw.d_sharp;
//        if (mFSharpKey.contains(point.x, point.y))
//            return R.raw.f_sharp;
//        if (mGSharpKey.contains(point.x, point.y))
//            return R.raw.g_sharp;
//        if (mASharpKey.contains(point.x, point.y))
//            return R.raw.a_sharp;
//
//        if (mCKey.contains(point.x, point.y))
//            return R.raw.c;
//        if (mDKey.contains(point.x, point.y))
//            return R.raw.d;
//        if (mEKey.contains(point.x, point.y))
//            return R.raw.e;
//        if (mFKey.contains(point.x, point.y))
//            return R.raw.f;
//        if (mGKey.contains(point.x, point.y))
//            return R.raw.g;
//        if (mAKey.contains(point.x, point.y))
//            return R.raw.a;
//        if (mBKey.contains(point.x, point.y))
//            return R.raw.b;

        return -1;
    }
}
PianoKeyboard.java

例二:PaintView

public class PaintView extends View {
    public static final int MAX_FINGERS = 5;
    private Path[] mFingerPaths = new Path[MAX_FINGERS];
    private Paint mFingerPaint;
    private ArrayList<Path> mCompletedPaths;
    private RectF mPathBounds = new RectF();

    public PaintView(Context context) {
        super(context);
    }

    public PaintView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public PaintView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        mCompletedPaths = new ArrayList<Path>();
        mFingerPaint = new Paint();
        mFingerPaint.setAntiAlias(true);
        mFingerPaint.setColor(Color.BLACK);
        mFingerPaint.setStyle(Paint.Style.STROKE);
        mFingerPaint.setStrokeWidth(6);
        mFingerPaint.setStrokeCap(Paint.Cap.BUTT);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for(Path completedPath : mCompletedPaths){
            canvas.drawPath(completedPath, mFingerPaint);
        }
        for(Path fingerPath : mFingerPaths){
            if(fingerPath != null){
                canvas.drawPath(fingerPath, mFingerPaint);
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int pointerCount = event.getPointerCount();
        int cappedPointerCount = pointerCount > MAX_FINGERS ? MAX_FINGERS : pointerCount;

        int actionIndex = event.getActionIndex();
        int action = event.getActionMasked();
        int id = event.getPointerId(actionIndex);

        if((action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN)
                && id < MAX_FINGERS){
            mFingerPaths[id] = new Path();
            mFingerPaths[id].moveTo(event.getX(actionIndex), event.getY(actionIndex));
        }else if((action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP)
                && id < MAX_FINGERS){
            mFingerPaths[id].setLastPoint(event.getX(actionIndex), event.getY(actionIndex));
            mCompletedPaths.add(mFingerPaths[id]);
            mFingerPaths[id].computeBounds(mPathBounds, true);
            invalidate((int) mPathBounds.left, (int) mPathBounds.top,
                    (int) mPathBounds.right, (int) mPathBounds.bottom);
            mFingerPaths[id] = null;
        }
        for(int i = 0; i < cappedPointerCount; i++){
            if(mFingerPaths[i] != null){
                int index = event.findPointerIndex(i);
                mFingerPaths[i].lineTo(event.getX(index), event.getY(index));
                mFingerPaths[i].computeBounds(mPathBounds, true);
                invalidate((int) mPathBounds.left, (int) mPathBounds.top,
                        (int) mPathBounds.right, (int) mPathBounds.bottom);
            }
        }

        return true;
    }
}
PaintView.java

例三:RotationView

public class RotateView extends View {
    public static final String LOG_TAG = RotateView.class.getSimpleName();
    private static final double MAX_ANGLE = 1e-1;
    private Paint mPaint;
    private float mRotation;
    private Float mPreviousAngle;

    public RotateView(Context context) {
        super(context);
    }

    public RotateView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public RotateView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(10);
        mPaint.setAntiAlias(true);

        mPreviousAngle = null;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int width = getWidth();
        int height = getHeight();
        int radius = (int) (width > height ? height * 0.666f : width * 0.666f) / 2;

        canvas.drawColor(Color.YELLOW);
        canvas.drawCircle(width / 2, height / 2, radius, mPaint);
        canvas.drawRect(0, 50, 200, 100, mPaint);
        canvas.save();
        canvas.rotate(mRotation, width / 2, height / 2);
        canvas.drawLine(width / 2, height * 0.1f, width / 2, height * 0.9f, mPaint);
        canvas.drawRect(0, 50, 200, 100, mPaint);
        canvas.drawRect(500, 550, 700, 600, mPaint);
        canvas.drawCircle(width / 2, height / 2, radius - 200, mPaint);
        canvas.restore();
       // canvas.save();
        canvas.rotate(mRotation / 2, width / 2, height / 2);
        canvas.drawRect(500, 550, 700, 600, mPaint);
        canvas.restore();
        canvas.drawRect(500, 550, 700, 600, mPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(event.getPointerCount() == 2){
            float currentAngle = (float) angle(event);
            Log.d(LOG_TAG, "In onTouchEvent currentAngle = " + currentAngle);
            Log.d(LOG_TAG, "In onTouchEvent currentAngle degree = " + Math.toDegrees(currentAngle));
            if(mPreviousAngle != null){
                Log.d(LOG_TAG, "In onTouchEvent mPreviousAngle = " + mPreviousAngle);
                Log.d(LOG_TAG, "In onTouchEvent mPreviousAngle degree = " + Math.toDegrees(mPreviousAngle));
                Log.d(LOG_TAG, "In onTouchEvent mRotation = " + mRotation);
                Log.d(LOG_TAG, "In onTouchEvent mPreviousAngle - currentAngle = " + (mPreviousAngle - currentAngle));
                Log.d(LOG_TAG, "In onTouchEvent MAX_ANGLE = " + MAX_ANGLE);
                Log.d(LOG_TAG, "In onTouchEvent clamp = " + clamp(mPreviousAngle - currentAngle, -MAX_ANGLE, MAX_ANGLE));
                mRotation -= Math.toDegrees(clamp(mPreviousAngle - currentAngle,
                        -MAX_ANGLE, MAX_ANGLE)); // (float)
//                mRotation -= Math.toDegrees(mPreviousAngle - currentAngle);
                invalidate();
            }
            mPreviousAngle = currentAngle;
        }else{
            mPreviousAngle = null;
        }
        return true;
    }

    private static double angle(MotionEvent event){
        double deltaX = (event.getX(0) - event.getX(1));
        double deltaY = (event.getY(0) - event.getY(1));
        Log.d(LOG_TAG, "In angle x = " + deltaX + ", y = " + deltaY);
        return Math.atan2(deltaY, deltaX);
    }

    private static double clamp(double value, double min, double max){
        if(value < min){
            Log.d(LOG_TAG, "In clamp min ---------------------------------------------------------------------------------------------------------------------------------------");
            return min;
        }
        if(value > max){
            Log.d(LOG_TAG, "In clamp max ---------------------------------------------------------------------------------------------------------------------------------------");
            return max;
        }
        return value;
    }
}
RotateView.java

@、OpenGL ES

https://developer.android.com/guide/topics/graphics/opengl.html

1、  开源3D引擎Rajawali

https://github.com/Rajawali/Rajawali

2、  商用引擎Unity3D

http://unity3d.com/cn/

原文地址:https://www.cnblogs.com/yarightok/p/5630380.html