多屏幕
自定义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/

Android Programming: Pushing the Limits -- Chapter 5: Android User Interface Operations的更多相关文章

  1. Android Programming: Pushing the Limits -- Chapter 7:Android IPC -- ApiWrapper

    前面两片文章讲解了通过AIDL和Messenger两种方式实现Android IPC.而本文所讲的并不是第三种IPC方式,而是对前面两种方式进行封装,这样我们就不用直接把Aidl文件,java文件拷贝 ...

  2. Android Programming: Pushing the Limits -- Chapter 7:Android IPC -- Messenger

    Messenger类实际是对Aidl方式的一层封装.本文只是对如何在Service中使用Messenger类实现与客户端的通信进行讲解,对Messenger的底层不做说明.阅读Android Prog ...

  3. Android Programming: Pushing the Limits -- Chapter 7:Android IPC -- AIDL

    服务端: 最终项目结构: 这个项目中,我们将用到自定义类CustomData作为服务端与客户端传递的数据. Step 1:创建CustomData类 package com.ldb.android.e ...

  4. Android Programming: Pushing the Limits -- Chapter 4: Android User Experience and Interface Design

    User Stories Android UI Design 附加资源 User Stories: @.通过写故事来设计应用. @.每个故事只关注一件事. @.不同的故事可能使用相同的组件,因此尽早地 ...

  5. Android Programming: Pushing the Limits -- Chapter 3: Components, Manifests, and Resources

    Android Components Manifest文件 Resource and Assets v\:* {behavior:url(#default#VML);} o\:* {behavior: ...

  6. Android Programming: Pushing the Limits -- Chapter 2: Efficient Java Code for Android

    Android's Dalvik Java 与 Java SE 进行比较 Java代码优化 内存管理与分配 Android的多线程操作 Android’s Dalvik Java 与 Java SE ...

  7. Android Programming: Pushing the Limits -- Chapter 1: Fine-Tuning Your Development Environment

    ADB命令 Application Exerciser Monkey Gradle ProGuard 代码重用 版本控制 静态代码分析 代码重构 开发者模式   ADB命令: @.adb help:查 ...

  8. Android Programming: Pushing the Limits -- Chapter 6: Services and Background Tasks

    什么时候使用Service 服务类型 开启服务 后台运行 服务通信 附加资源 什么时候使用Service: @.任何与用户界面无关的操作,可移到后台线程,然后由一个Service来控制这个线程. 服务 ...

  9. [iOS翻译]《iOS 7 Programming Pushing the Limits》系列:你可能不知道的Objective-C技巧

    简介: 如果你阅读这本书,你可能已经牢牢掌握iOS开发的基础,但这里有一些小特点和实践是许多开发者并不熟悉的,甚至有数年经验的开发者也是.在这一章里,你会学到一些很重要的开发技巧,但这仍远远不够,你还 ...

随机推荐

  1. Java 的printf(转)

    出处:http://blog.csdn.net/swandragon/article/details/4653600 public class TestPrintf{public static voi ...

  2. ORA-02287: 此处不允许序号

    今天使用 insert into select 时出现了这个异常,感觉很诡异,去metalink查了下资料,找出了错误原因,记录下来. SQL> CREATE TABLE test_baser0 ...

  3. Caffe学习系列(14):Caffe代码阅读

    知乎上这位博主画的caffe的整体结构:https://zhuanlan.zhihu.com/p/21796890?refer=hsmyy Caffe 做train时的流程图,来自http://caf ...

  4. memcache 与 mencached扩展的区别

    memcache是一套分布式的高速缓存系统,由LiveJournal的Brad Fitzpatrick开发,但目前被许多网站使用以提升网站的访问速度,尤其对于一些大型的.需要频繁访问数据库的网站访问速 ...

  5. jdk 1.7 在ubuntu 环境配置

    在/opt/里解压了jdk 1.7后 设置环境变量 chen@caicai ~ $ vim .profile export JAVA_HOME=/opt/jdk1..0_79 export JRE_H ...

  6. python entrypoint

    entrypoint, 实际是一张匹配表.匹配简短指令和具体的python函数的执行路径.有点快捷方式的概念. 不同的是,这种快捷方式不仅可以给命令行使用,还可以供其他代码简单调用,而无需关注太多细节 ...

  7. Python统计百分比及排序

    source.txt: 60行 89 91 93 90 92 92 94 92 89 95 93 92 90 92 93 94 94 92 90 92 92 92 ... 统计各个值的百分比,并排序 ...

  8. Git的维护(git gc和git fsck)

    原文: http://gitbook.liuhui998.com/4_10.html 一.保证git良好的性能 在大的仓库中, git靠压缩历史信息来节约磁盘和内存空间. 压缩操作并不是自动进行的, ...

  9. 为MongoDB创建一个Windows服务

    一:选型,根据机器的操作系统类型来选择合适的版本,使用下面的命令行查询机器的操作系统版本 wmic os get osarchitecture 二:下载并安装 附上下载链接 点击安装包,我这里是把文件 ...

  10. C++中string,wstring,CString的基本概念和用法

    一.概念 string和CString均是字符串模板类,string为标准模板类(STL)定义的字符串类,已经纳入C++标准之中.wstring是操作宽字符串的类.C++标准程序库对于string的设 ...