2015-07-22 11 views
5

Ho iniziato a studiare disegno su tela con Android, e vorrei fare una semplice app.Il disegno su tela con interazione utente è un po 'laggoso

Sull'app avvia un cosiddetto 'serpente' che inizia a muoversi sullo schermo, quando l'utente tocca lo schermo, il 'serpente' cambia direzione.

I achived questo facilmente ma c'è un piccolo problema:

Quando l'utente tocca lo schermo, il serpente volte cambia direzione in quel particolare momento, a volte solo dopo alcuni millisecondi. Quindi l'utente può chiaramente ritenere che l'interazione sia non così reattiva come dovrebbe, l'ora esatta di svolta del serpente è abbastanza imprevedibile anche se ci si concentra molto duramente. Ci deve essere un altro modo per farlo meglio di quanto ho fatto io.

Controllare prego il mio codice, utilizzo un gestore con Runnable per spostare il serpente. (a matita su tela e ogni impostandola come sfondo di una visione in tempo, che è l'impostazione con setContentView alla mia attività ogni volta

Codice:.

public class MainActivity extends Activity implements View.OnClickListener { 

    Paint paint = new Paint(); 
    Canvas canvas; 


    View contentView; ///<The view to set by setContentView 

    int moveSize; ///<Sze in px to move drawer to draw another square 


    int leftIndex; ///<Indexes for square drawing 
    int topIndex; 
    int rightIndex; 
    int bottomIndex; 

    int maxBoundsX; ///<The max number of squares in X axis 
    int maxBoundsY; ///<The max number of squares in Y axis 

    int moveSpeedInMillis = 25; ///<One movement square per milliseconds 

    Bitmap bitmapToDrawOn; ///<We draw the squares to this bitmap 

    Direction currentDirection = Direction.RIGHT; ///< RIGHT,LEFT,UP,DOWN directions. default is RIGHT 

    Handler handler = new Handler(); ///<Handler for running a runnable that actually 'moves' the snake by drawing squares to shifted positions 
    Runnable runnable = new Runnable() { 

     @Override 
     public void run() { 

      Log.i("runnable", "ran"); 

      //Drawing a square to the current destination 
      drawRectPls(currentDirection); 
      //After some delay we call again and again and again 
      handler.postDelayed(runnable, moveSpeedInMillis); 
     } 
    }; 


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


     /*getting area properties like moveSize, and bounds*/ 

     moveSize = searchForOptimalMoveSize(); 

     maxBoundsX = ScreenSizer.getScreenWidth(this)/moveSize; 
     maxBoundsY = ScreenSizer.getScreenHeight(this)/moveSize; 

     Log.i("moveSize", "moveSize: " + moveSize); 
     Log.i("maxBounds: ", "x: " + maxBoundsX + " ; " + "y: " + maxBoundsY); 


     /*setting start pos*/ 

     //We start on the lower left part of the screen 

     leftIndex = moveSize * (-1); 
     rightIndex = 0; 
     bottomIndex = moveSize * maxBoundsY; 
     topIndex = moveSize * (maxBoundsY - 1); 


     //Setting contentView, bitmap, and canvas 

     contentView = new View(this); 
     contentView.setOnClickListener(this); 

     bitmapToDrawOn = Bitmap.createBitmap(ScreenSizer.getScreenWidth(this), ScreenSizer.getScreenHeight(this), Bitmap.Config.ARGB_8888); 
     canvas = new Canvas(bitmapToDrawOn); 

     contentView.setBackground(new BitmapDrawable(getResources(), bitmapToDrawOn)); 
     setContentView(contentView); 

     /*starts drawing*/ 
     handler.post(runnable); 


    } 

    /** 
    * Draws a square to the next direction 
    * 
    * @param direction the direction to draw the next square 
    */ 
    private void drawRectPls(Direction direction) { 

     if (direction.equals(Direction.RIGHT)) { 
      leftIndex += moveSize; 
      rightIndex += moveSize; 
     } else if (direction.equals(Direction.UP)) { 
      topIndex -= moveSize; 
      bottomIndex -= moveSize; 
     } else if (direction.equals(Direction.LEFT)) { 
      leftIndex -= moveSize; 
      rightIndex -= moveSize; 
     } else if (direction.equals(Direction.DOWN)) { 
      topIndex += moveSize; 
      bottomIndex += moveSize; 
     } 


     addRectToCanvas(); 
     contentView.setBackground(new BitmapDrawable(getResources(), bitmapToDrawOn)); 
     Log.i("drawRect", "direction: " + currentDirection); 

    } 


    private void addRectToCanvas() { 
     paint.setColor(Color.argb(255, 100, 100, 255)); 
     canvas.drawRect(leftIndex, topIndex, rightIndex, bottomIndex, paint); 
    } 

    /** 
    * Upon tapping the screen the the snake is changing direction, one way simple interaction 
    */ 
    @Override 
    public void onClick(View v) { 

     if (v.equals(contentView)) { 

      if (currentDirection.equals(Direction.RIGHT)) { 
       currentDirection = Direction.UP; 
      } else if (currentDirection.equals(Direction.UP)) { 
       currentDirection = Direction.LEFT; 
      } else if (currentDirection.equals(Direction.LEFT)) { 
       currentDirection = Direction.DOWN; 
      } else if (currentDirection.equals(Direction.DOWN)) { 
       currentDirection = Direction.RIGHT; 
      } 
     } 
    } 


    /** 
    * Just getting the size of a square. Searching for an integer that divides both screen's width and height 
    * @return 
    */ 
    private int searchForOptimalMoveSize() { 
     int i; 
     for (i = 16; i <= 64; i++) { 
      Log.i("iter", "i= " + i); 
      if (ScreenSizer.getScreenWidth(this) % i == 0) { 
       Log.i("iter", ScreenSizer.getScreenWidth(this) + "%" + i + " =0 !"); 
       if (ScreenSizer.getScreenHeight(this) % i == 0) { 
        Log.i("iter", ScreenSizer.getScreenHeight(this) + "%" + i + " =0 !"); 
        return i; 
       } 
      } 
     } 
     return -1; 
    } 


    /** 
    * Stops the handler 
    */ 
    @Override 
    protected void onDestroy() { 
     super.onDestroy(); 
     handler.removeCallbacks(runnable); 
    } 
} 

EDIT:

Ho modificato il mio codice, ora la vista contiene tutti i dettagli e io uso il suDraw e invalidano i metodi come Phi suggerito da lipp.

Il risultato è un po 'meglio ma posso ancora sentire chiaramente che l'interazione dell'utente si traduce in un cambiamento di direzione laggy.

Forse qualcosa che dovrei fare con i fili?

public class SpiralView extends View implements View.OnClickListener { 

    int leftIndex; ///<Indexes for square drawing 
    int topIndex; 
    int rightIndex; 
    int bottomIndex; 

    int speedInMillis = 50; 

    int moveSize; ///<Sze in px to move drawer to draw another square 

    int maxBoundsX; ///<The max number of squares in X axis 
    int maxBoundsY; ///<The max number of squares in Y axis 


    Paint paint = new Paint(); 
    Direction currentDirection = Direction.RIGHT; ///< RIGHT,LEFT,UP,DOWN directions. default is RIGHT 


    public void setUp(int moveSize, int maxBoundsX, int maxBoundsY) { 
     this.moveSize = moveSize; 
     this.maxBoundsX = maxBoundsX; 
     this.maxBoundsY = maxBoundsY; 
     this.leftIndex = moveSize * (-1); 
     this.rightIndex = 0; 
     this.bottomIndex = moveSize * (maxBoundsY); 
     this.topIndex = moveSize * (maxBoundsY - 1); 

    } 

    public SpiralView(Context context, AttributeSet attrs) { 
     super(context, attrs); 
     setOnClickListener(this); 


    } 

    /** 
    * Draws a square to the next direction 
    * 
    * @param direction the direction to draw the next square 
    */ 
    private void drawOnPlease(Direction direction, Canvas canvas) { 

     if (direction.equals(Direction.RIGHT)) { 
      leftIndex += moveSize; 
      rightIndex += moveSize; 
     } else if (direction.equals(Direction.UP)) { 
      topIndex -= moveSize; 
      bottomIndex -= moveSize; 
     } else if (direction.equals(Direction.LEFT)) { 
      leftIndex -= moveSize; 
      rightIndex -= moveSize; 
     } else if (direction.equals(Direction.DOWN)) { 
      topIndex += moveSize; 
      bottomIndex += moveSize; 
     } 


     Log.i("drawRect", "direction: " + currentDirection); 
     Log.i("drawRect", "indexes: "+topIndex+" , "+rightIndex+" ," +bottomIndex+" , "+leftIndex); 

     addRectToCanvas(canvas); 

    } 

    private void addRectToCanvas(Canvas canvas) { 
     paint.setColor(Color.argb(255, 100, 100, 255)); 

     canvas.drawRect(leftIndex, topIndex, rightIndex, bottomIndex, paint); 
    } 


    @Override 
    protected void onDraw(Canvas canvas) { 

     try { 

      drawOnPlease(currentDirection, canvas); 

      synchronized (this) { 
       wait(speedInMillis); 
      } 


      invalidate(); 

     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 

    } 

    @Override 
    public void onClick(View v) { 

     if (currentDirection.equals(Direction.RIGHT)) { 
      currentDirection = Direction.UP; 
     } else if (currentDirection.equals(Direction.UP)) { 
      currentDirection = Direction.LEFT; 
     } else if (currentDirection.equals(Direction.LEFT)) { 
      currentDirection = Direction.DOWN; 
     } else if (currentDirection.equals(Direction.DOWN)) { 
      currentDirection = Direction.RIGHT; 
     } 

     //..? 
     invalidate(); 

    } 

} 
+0

Invalidate() all'interno di onDraw() è una cattiva idea - causerà chiamate ricorsive suDraw(). Rimuovilo e probabilmente starai bene. – Andrey

+0

@ ruan65 Allora cosa disegnerà periodicamente il serpente? –

+0

'wait' sul thread ui è ** sempre ** una cattiva idea – Blackbelt

risposta

1

Il numero magico è 16 millisecondi per Android per ridisegnare la vista senza framedrops.

Controlla questo video dagli sviluppatori Android che spiega questo. https://www.youtube.com/watch?v=CaMTIgxCSqU&index=25&list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE

controllare Soprattutto questo video https://youtu.be/vkTn3Ule4Ps?t=54

E spiega come utilizzare tela clipping al fine di non richiamare tutta la superficie in ogni ciclo, il dado disegnare solo ciò che è necessario per essere disegnare.

1

Non utilizzare un gestore per disegnare con Canvas. Invece dovresti creare una vista personalizzata e usare il metodo onDraw (Canvas canvas). In questo metodo è possibile disegnare sull'oggetto Canvas come già fatto. Chiamando il metodo invalidate() si attiva una nuova chiamata onDraw(). Nella funzione onTouch() o onClick() anche innescare una nuova chiamata OnDraw chiamando invalida()

class SnakView extends View { 
    @Override 
    protected void onDraw(Canvas canvas) { 
    // draw on canvas 
    invalidate(); 
    } 

    @Override 
    public void onClick(View v) { 
    // handle the event 
    invalidate(); 
    } 
} 
+0

E come posso" aspettare "su onDraw se non posso usare Handler? L'accelerazione hardware –

0

Si può provare e aggiungere android:hardwareAccelerated="true" al manifesta, al o.

Questo funzionerà per dispositivi con 3.0+.

anche il livello di api obiettivo dovrebbe essere 11.

allora funzionerà più agevolmente.

+0

è attivata per impostazione predefinita a partire da Android 4, quindi dubito che ciò risolva il problema dell'OP. – aga