ドットのグリッドを描画するアプリがあります(たとえば5x5)。ユーザーは、そのグリッド上に線を引くように求められます。ユーザーの指がグリッド内のドットの1つに触れると、このドットが描画されたパスの一部であることを示すために、このドットに色が付けられます。さらに、タッチした2つのドットの間に線が引かれます。
問題-パフォーマンスが非常に悪くなり、いくつかの原因が発生します。
- アプリケーションが非常に遅くなります。
- モーションイベントの
event.getAction()
粒度が悪くなります。つまりenter code here
、たとえば10ピクセルごとに動きを登録するのではなく、100ピクセルごとに動きを登録します。これにより、ユーザーがタッチしたドットがアプリに再描画されなくなります。 - モーション座標が単純に間違っている場合があります。ユーザーが指をピクセル100からピクセル500に移動しているとすると、読み取り値には100 ... 200 ... 150 ... 140 ... 300...400と表示される場合があります。何らかの理由で、タッチ位置がめちゃくちゃになる場合があります。
ユーザーがタッチしたドットをアプリが「見逃し」、緑色のドットを描画しない例を見てください。
私はいくつかのことを試しました:
- Thread.sleep(100);を追加します。
else if(event.getAction() == MotionEvent.ACTION_MOVE)
内部にonTouchEvent(MotionEvent event)
、私はこれがすべてのそれらのタッチイベントに追いつくためにCPU時間を与えるかもしれないことを読みました-物事を変えませんでした this.destroyDrawingCache()
最後に追加しdoDraw()
ます(私が使用した1つのチュートリアルで示唆されているように、onDrawの代わりに使用します)。これにより、システムの速度が低下しているように見えるすべてのイベント/描画キャッシュがクリアされると思いましたが、何も変わりませんでした。
私はAndroidアニメーションにかなり慣れていないので、どのように進めるかわかりません。
doDraw()
(my onDraw())とでできる限りのことはしないでくださいonTouchEvent()
。- 私はそれについていくつかのことを読みまし
invalidate()
たが、それをいつどのように使用するかわかりません。doDraw()
正しく理解していれば、呼び出されるたびにビューが新しく描画されます。たとえば、私のグリッドは静的です-どうすればグリッドの再描画を回避できますか?
++++++++++++++++++++++++10月7日更新+++++++++++++++++++++
canvas.drawCircle(xPos, yPos, 8, mNodePaint);
の代わりに使ってみましcanvas.drawBitmap(mBitmap, xPos, yPos, null);
た。実際のビットマップを使用しなかった場合、パフォーマンスが向上する可能性があると思いました。実際のところ、そうではありませんでした。このような単純なアプリケーションがデバイスにこのような重い負荷をかける可能性があることについて、私は少し混乱しています。私は本当に間違った方法で何かをしているに違いありません。
++++++++++++++++++++++++10月12日更新+++++++++++++++++++++
@LadyWoodiが提案したことを考慮に入れました-ループからすべての変数宣言を削除しました-とにかくそれは悪い習慣であり、アプリの動作をログに記録できるように、使用するすべての「System.Out」行も削除しましたなぜ私がそのような不完全なパフォーマンスを得るのかをよりよく理解してください。パフォーマンスに変化があった場合(実際にはフレームレートの変化を測定しなかった)、それは無視できると言って悲しいです。
他のアイデアはありますか?
++++++++++++++++++++++++10月13日更新+++++++++++++++++++++
- ゲーム中に変更されないドットの静的グリッド(screenShotの中空の黒/白のドットを参照)があるため、次のようにしました。
-グリッドを1回描画します。
-を使用して、図面をビットマップとしてキャプチャしBitmap.createBitmap()
ます。
-静的canvas.drawBitmap()
ドットグリッドのビットマップを描画するために使用します。
-スレッドが実行されると、ドットのグリッドが描画されていることを確認します。実行中の場合、静的ドットグリッドを再作成しません。以前にレンダリングしたビットマップからのみレンダリングします。
驚いたことに、これは私のパフォーマンスには何の変化もありませんでした!毎回ドットグリッドを再描画しても、アプリのパフォーマンスに真の視覚効果はありませんでした。
canvas = mHolder.lockCanvas(new Rect(50, 50, 150, 150));
描画スレッド内で使用することにしました。毎回レンダリングされる領域を制限するかどうかを確認するためだけにテストを行ったので、パフォーマンスを向上させることができます。これも役に立ちませんでした。次に、EclipseのDDMSツールを使用して、アプリのプロファイルを作成しました。それが思いついたのは、
canvas.drawPath(path, mPathPaint);
(Canvas.native_drawPath)がCPU時間の約88.5%を消費したということでした!!!
しかし、なぜ??!私のパスの描画はかなり単純です。mGraphicsにはパスのコレクションが含まれています。各パスがゲーム画面の境界内にあるかどうかを確認してから、パスを描画します。
//draw path user is creating with her finger on screen
for (Path path : mGraphics)
{
//get path values
mPm = new PathMeasure(path, true);
mPm.getPosTan(0f, mStartCoordinates, null);
//System.out.println("aStartCoordinates X:" + aStartCoordinates[0] + " aStartCoordinates Y:" + aStartCoordinates[1]);
mPm.getPosTan(mPm.getLength(), mEndCoordinates, null);
//System.out.println("aEndCoordinates X:" + aEndCoordinates[0] + " aEndCoordinates Y:" + aEndCoordinates[1]);
//coordinates are within game board boundaries
if((mStartCoordinates[0] >= 1 && mStartCoordinates[1] >= 1) && (mEndCoordinates[0] >= 1 && mEndCoordinates[1] >= 1))
{
canvas.drawPath(path, mPathPaint);
}
}
私の例では、プログラムされていないコード行を誰かが見ることができますか?
++++++++++++++++++++++++更新10月14日+++++++++++++++++++++
メソッドに変更を加えましたdoDraw()
。基本的に私がしているのは、何かが変更された場合にのみ画面を描画することです。他のすべての場合、私は単に画面のキャッシュされたビットマップを保存してレンダリングします。ご覧ください:
public void doDraw(Canvas canvas)
{
synchronized (mViewThread.getSurefaceHolder())
{
if(mGraphics.size() > mPathsCount)
{
mPathsCount = mGraphics.size();
//draw path user is creating with her finger on screen
for (Path path : mGraphics)
{
//get path values
mPm = new PathMeasure(path, true);
mPm.getPosTan(0f, mStartCoordinates, null);
//System.out.println("aStartCoordinates X:" + aStartCoordinates[0] + " aStartCoordinates Y:" + aStartCoordinates[1]);
mPm.getPosTan(mPm.getLength(), mEndCoordinates, null);
//System.out.println("aEndCoordinates X:" + aEndCoordinates[0] + " aEndCoordinates Y:" + aEndCoordinates[1]);
//coordinates are within game board boundaries
if((mStartCoordinates[0] >= 1 && mStartCoordinates[1] >= 1) && (mEndCoordinates[0] >= 1 && mEndCoordinates[1] >= 1))
{
canvas.drawPath(path, mPathPaint);
}
}
//nodes that the path goes through, are repainted green
//these nodes are building the drawn pattern
for (ArrayList<PathPoint> nodePattern : mNodesHitPatterns)
{
for (PathPoint nodeHit : nodePattern)
{
canvas.drawBitmap(mDotOK, nodeHit.x - ((mDotOK.getWidth()/2) - (mNodeBitmap.getWidth()/2)), nodeHit.y - ((mDotOK.getHeight()/2) - (mNodeBitmap.getHeight()/2)), null);
}
}
mGameField = Bitmap.createBitmap(mGridNodesCount * mNodeGap, mGridNodesCount * mNodeGap, Bitmap.Config.ARGB_8888);
}
else
{
canvas.drawBitmap(mGameField, 0f, 0f, null);
}
結果については、デバイスがパスをレンダリングする必要がなく、ビットマップから描画するだけである限り、処理は非常に高速になります。しかし、パフォーマンスを使用して画面を再レンダリングする必要がある瞬間はcanvas.drawPath()
、モルヒネのカメのように遅くなります...パスが多いほど(最大6つ以上、これは何もありません!)、レンダリングが遅くなります。これはどれくらい奇妙ですか?-私の道は本当に曲がりくねっていません-時折曲がるすべての直線です。私が言いたいのは、その線はそれほど「複雑」ではないということです。
以下にコードを追加しました-改善のアイデアがあれば。
よろしくお願いします、D。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~クラス「パネル」~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
public class Panel extends SurfaceView implements SurfaceHolder.Callback {
Bitmap mNodeBitmap;
int mNodeBitmapWidthCenter;
int mNodeBitmapHeightCenter;
Bitmap mDotOK;
ViewThread mViewThread;
ArrayList<PathPoint> mPathPoints;
private ArrayList<Path> mGraphics = new ArrayList<Path>(3);
private ArrayList<ArrayList<PathPoint>> mNodesHitPatterns = new ArrayList<ArrayList<PathPoint>>();
private Paint mPathPaint;
Path mPath = new Path();
//private ArrayList<Point> mNodeCoordinates = new ArrayList<Point>();
private int mGridNodesCount = 5;
private int mNodeGap = 100;
PathPoint mNodeCoordinates[][] = new PathPoint[mGridNodesCount][mGridNodesCount];
PathMeasure mPm;
float mStartCoordinates[] = {0f, 0f};
float mEndCoordinates[] = {0f, 0f};
PathPoint mPathPoint;
Boolean mNodesGridDrawn = false;
Bitmap mGameField = null;
public Boolean getNodesGridDrawn() {
return mNodesGridDrawn;
}
public Panel(Context context) {
super(context);
mNodeBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dot);
mNodeBitmapWidthCenter = mNodeBitmap.getWidth()/2;
mNodeBitmapHeightCenter = mNodeBitmap.getHeight()/2;
mDotOK = BitmapFactory.decodeResource(getResources(), R.drawable.dot_ok);
getHolder().addCallback(this);
mViewThread = new ViewThread(this);
mPathPaint = new Paint();
mPathPaint.setAntiAlias(true);
mPathPaint.setDither(true); //for better color
mPathPaint.setColor(0xFFFFFF00);
mPathPaint.setStyle(Paint.Style.STROKE);
mPathPaint.setStrokeJoin(Paint.Join.ROUND);
mPathPaint.setStrokeCap(Paint.Cap.ROUND);
mPathPaint.setStrokeWidth(5);
}
public ArrayList<ArrayList<PathPoint>> getNodesHitPatterns()
{
return this.mNodesHitPatterns;
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
public void surfaceCreated(SurfaceHolder holder) {
//setPadding(100, 100, 0, 0);
if (!mViewThread.isAlive()) {
mViewThread = new ViewThread(this);
mViewThread.setRunning(true);
mViewThread.start();
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
if (mViewThread.isAlive()) {
mViewThread.setRunning(false);
}
}
//draw the basic nodes grid that the user will use to draw the lines on
//store as bitmap
public void drawNodesGrid(Canvas canvas)
{
canvas.drawColor(Color.WHITE);
for (int i = 0; i < mGridNodesCount; i++)
{
for (int j = 0; j < mGridNodesCount; j++)
{
int xPos = j * mNodeGap;
int yPos = i * mNodeGap;
try
{
//TODO - changed
mNodeCoordinates[i][j] = new PathPoint(xPos, yPos, null);
}
catch (Exception e)
{
e.printStackTrace();
}
canvas.drawBitmap(mNodeBitmap, xPos, yPos, null);
}
}
mNodesGridDrawn = true;
mGameField = Bitmap.createBitmap(mGridNodesCount * mNodeGap, mGridNodesCount * mNodeGap, Bitmap.Config.ARGB_8888);
}
public void doDraw(Canvas canvas)
{
canvas.drawBitmap(mGameField, 0f, 0f, null);
synchronized (mViewThread.getSurefaceHolder())
{
//draw path user is creating with her finger on screen
for (Path path : mGraphics)
{
//get path values
mPm = new PathMeasure(path, true);
mPm.getPosTan(0f, mStartCoordinates, null);
//System.out.println("aStartCoordinates X:" + aStartCoordinates[0] + " aStartCoordinates Y:" + aStartCoordinates[1]);
mPm.getPosTan(mPm.getLength(), mEndCoordinates, null);
//System.out.println("aEndCoordinates X:" + aEndCoordinates[0] + " aEndCoordinates Y:" + aEndCoordinates[1]);
//coordinates are within game board boundaries
if((mStartCoordinates[0] >= 1 && mStartCoordinates[1] >= 1) && (mEndCoordinates[0] >= 1 && mEndCoordinates[1] >= 1))
{
canvas.drawPath(path, mPathPaint);
}
}
//nodes that the path goes through, are repainted green
//these nodes are building the drawn pattern
for (ArrayList<PathPoint> nodePattern : mNodesHitPatterns)
{
for (PathPoint nodeHit : nodePattern)
{
canvas.drawBitmap(mDotOK, nodeHit.x - ((mDotOK.getWidth()/2) - (mNodeBitmap.getWidth()/2)), nodeHit.y - ((mDotOK.getHeight()/2) - (mNodeBitmap.getHeight()/2)), null);
}
}
this.destroyDrawingCache();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
synchronized (mViewThread.getSurefaceHolder()) {
if(event.getAction() == MotionEvent.ACTION_DOWN)
{
//System.out.println("Action downE x: " + event.getX() + " y: " + event.getY());
for (int i = 0; i < mGridNodesCount; i++)
{
for (int j = 0; j < mGridNodesCount; j++)
{
//TODO - changed
//PathPoint pathPoint = mNodeCoordinates[i][j];
mPathPoint = mNodeCoordinates[i][j];
if((Math.abs((int)event.getX() - mPathPoint.x) <= 35) && (Math.abs((int)event.getY() - mPathPoint.y) <= 35))
{
//mPath.moveTo(pathPoint.x + mBitmap.getWidth() / 2, pathPoint.y + mBitmap.getHeight() / 2);
//System.out.println("Action down x: " + pathPoint.x + " y: " + pathPoint.y);
ArrayList<PathPoint> newNodesPattern = new ArrayList<PathPoint>();
mNodesHitPatterns.add(newNodesPattern);
//mNodesHitPatterns.add(nh);
//pathPoint.setAction("down");
break;
}
}
}
}
else if(event.getAction() == MotionEvent.ACTION_MOVE)
{
final int historySize = event.getHistorySize();
//System.out.println("historySize: " + historySize);
//System.out.println("Action moveE x: " + event.getX() + " y: " + event.getY());
coordinateFound:
for (int i = 0; i < mGridNodesCount; i++)
{
for (int j = 0; j < mGridNodesCount; j++)
{
//TODO - changed
//PathPoint pathPoint = mNodeCoordinates[i][j];
mPathPoint = mNodeCoordinates[i][j];
if((Math.abs((int)event.getX() - mPathPoint.x) <= 35) && (Math.abs((int)event.getY() - mPathPoint.y) <= 35))
{
int lastPatternIndex = mNodesHitPatterns.size()-1;
ArrayList<PathPoint> lastPattern = mNodesHitPatterns.get(lastPatternIndex);
int lastPatternLastNode = lastPattern.size()-1;
if(lastPatternLastNode != -1)
{
if(!mPathPoint.equals(lastPattern.get(lastPatternLastNode).x, lastPattern.get(lastPatternLastNode).y))
{
lastPattern.add(mPathPoint);
//System.out.println("Action moveC [add point] x: " + pathPoint.x + " y: " + pathPoint.y);
}
}
else
{
lastPattern.add(mPathPoint);
//System.out.println("Action moveC [add point] x: " + pathPoint.x + " y: " + pathPoint.y);
}
break coordinateFound;
}
else //no current match => try historical
{
if(historySize > 0)
{
for (int k = 0; k < historySize; k++)
{
//System.out.println("Action moveH x: " + event.getHistoricalX(k) + " y: " + event.getHistoricalY(k));
if((Math.abs((int)event.getHistoricalX(k) - mPathPoint.x) <= 35) && (Math.abs((int)event.getHistoricalY(k) - mPathPoint.y) <= 35))
{
int lastPatternIndex = mNodesHitPatterns.size()-1;
ArrayList<PathPoint> lastPattern = mNodesHitPatterns.get(lastPatternIndex);
int lastPatternLastNode = lastPattern.size()-1;
if(lastPatternLastNode != -1)
{
if(!mPathPoint.equals(lastPattern.get(lastPatternLastNode).x, lastPattern.get(lastPatternLastNode).y))
{
lastPattern.add(mPathPoint);
//System.out.println("Action moveH [add point] x: " + pathPoint.x + " y: " + pathPoint.y);
}
}
else
{
lastPattern.add(mPathPoint);
//System.out.println("Action moveH [add point] x: " + pathPoint.x + " y: " + pathPoint.y);
}
break coordinateFound;
}
}
}
}
}
}
}
else if(event.getAction() == MotionEvent.ACTION_UP)
{
// for (int i = 0; i < mGridSize; i++) {
//
// for (int j = 0; j < mGridSize; j++) {
//
// PathPoint pathPoint = mNodeCoordinates[i][j];
//
// if((Math.abs((int)event.getX() - pathPoint.x) <= 35) && (Math.abs((int)event.getY() - pathPoint.y) <= 35))
// {
// //the location of the node
// //mPath.lineTo(pathPoint.x + mBitmap.getWidth() / 2, pathPoint.y + mBitmap.getHeight() / 2);
//
// //System.out.println("Action up x: " + pathPoint.x + " y: " + pathPoint.y);
//
// //mGraphics.add(mPath);
// // mNodesHit.add(pathPoint);
// // pathPoint.setAction("up");
// break;
// }
// }
// }
}
//System.out.println(mNodesHitPatterns.toString());
//create mPath
for (ArrayList<PathPoint> nodePattern : mNodesHitPatterns)
{
for (int i = 0; i < nodePattern.size(); i++)
{
if(i == 0) //first node in pattern
{
mPath.moveTo(nodePattern.get(i).x + mNodeBitmapWidthCenter, nodePattern.get(i).y + mNodeBitmapHeightCenter);
}
else
{
mPath.lineTo(nodePattern.get(i).x + mNodeBitmapWidthCenter, nodePattern.get(i).y + mNodeBitmapWidthCenter);
}
//mGraphics.add(mPath);
}
}
mGraphics.add(mPath);
return true;
}
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~クラス「ViewThread」~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
public class ViewThread extends Thread {
private Panel mPanel;
private SurfaceHolder mHolder;
private boolean mRun = false;
public ViewThread(Panel panel) {
mPanel = panel;
mHolder = mPanel.getHolder();
}
public void setRunning(boolean run) {
mRun = run;
}
public SurfaceHolder getSurefaceHolder()
{
return mHolder;
}
@Override
public void run()
{
Canvas canvas = null;
while (mRun)
{
canvas = mHolder.lockCanvas();
//canvas = mHolder.lockCanvas(new Rect(50, 50, 150, 150));
if (canvas != null)
{
if(!mPanel.getNodesGridDrawn())
{
mPanel.drawNodesGrid(canvas);
}
mPanel.doDraw(canvas);
mHolder.unlockCanvasAndPost(canvas);
}
}
}
}