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

时间:2023-03-08 18:42:49
多屏幕
自定义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/