标签:
多屏幕自定义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); } } }
<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>
<?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>
@、在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; } }
例二: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; } }
例三: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; } }
@、OpenGL ES
https://developer.android.com/guide/topics/graphics/opengl.html
1、 开源3D引擎Rajawali
https://github.com/Rajawali/Rajawali
2、 商用引擎Unity3D
Android Programming: Pushing the Limits -- Chapter 5: Android User Interface Operations
标签:
原文地址:http://www.cnblogs.com/yarightok/p/5630380.html