2015-05-14 5 views
5

Ich möchte in meiner App einfache Linie Plot mit Echtzeit-Zeichnung machen. Ich weiß, dass es viele verschiedene Bibliotheken gibt, aber sie sind zu groß oder haben keine richtigen Funktionen oder Lizenzen.Zeichnung Plot in Echtzeit mit Android benutzerdefinierte Ansicht

Meine Idee ist es, benutzerdefinierte Ansicht und erweitern Sie einfach View Klasse. Mit OpenGL wäre in diesem Fall das Schießen auf eine Ente mit einem Kanon. Ich habe bereits eine Ansicht, die statische Daten zeichnet - das ist zuerst, ich setze alle Daten in float Array von meinem Plot Objekt und dann mit Schleife zeichnen alles in onDraw() Methode von PlotView Klasse.

Ich habe auch einen Thread, der neue Daten zu meinem Grundstück liefern wird. Aber das Problem ist jetzt, wie man es zeichnet, während neue Daten hinzugefügt werden. Der erste Gedanke war, einfach einen neuen Punkt hinzuzufügen und zu zeichnen. Füge noch einen und noch einmal hinzu. Aber ich bin mir nicht sicher, was bei 100 oder 1000 Punkten passieren wird. Ich füge neuen Punkt hinzu, frag Ansicht, um sich selbst ungültig zu machen, aber einige Punkte werden nicht gezeichnet. In diesem Fall kann es sogar schwierig sein, einige Warteschlangen zu verwenden, da die onDraw() wieder von vorne beginnt, so dass sich die Anzahl der Warteschlangenelemente erhöht.

Was würden Sie empfehlen, um dieses Ziel zu erreichen?

+1

Wie möchten Sie die Handlung zeichnen, wenn es viele Punkte gibt? Wird es scrollbar sein? Oder die Handlung wird nach links verschoben und alte Punkte werden versteckt? In jedem Fall können Sie nur den sichtbaren Teil des Diagramms neu zeichnen, so dass es nicht viel Zeichnen gibt. – esentsov

+0

Sind all diese Punkte gleichzeitig sichtbar? –

+0

@esentov Ich möchte es automatisch scrollen, wenn neue Punkte erscheinen. Ich brauche nur neue Punkte, damit die alten zerstört werden können. – sebap123

Antwort

1

Lassen Sie mich versuchen, das Problem ein wenig mehr zu skizzieren.

  • Sie haben eine Oberfläche, auf die Sie Punkte und Linien zeichnen können, und Sie wissen, wie Sie es aussehen lassen, wie Sie es aussehen möchten.
  • Sie verfügen über eine Datenquelle mit Punkten, die gezeichnet werden können, und diese Datenquelle wird im laufenden Betrieb geändert.
  • Sie möchten, dass die Oberfläche die eingehenden Daten so genau wie möglich widerspiegelt.
  • Die erste Frage ist - was ist mit Ihrer Situation ist langsam? Weißt du, woher deine Verspätungen kommen? Stellen Sie zuerst sicher, dass Sie ein Problem zu lösen haben; Zweitens, stellen Sie sicher, dass Sie wissen, woher Ihr Problem kommt.

    Angenommen, Ihr Problem in der Größe der Daten, die Sie bedeuten. Wie man das anspricht, ist eine komplexe Frage. Es hängt von den Eigenschaften der Daten ab, die grafisch dargestellt werden - welche Invarianten Sie annehmen können und so weiter. Sie haben über das Speichern von Daten in einer float[] gesprochen, also gehe ich davon aus, dass Sie eine feste Anzahl von Datenpunkten haben, die sich im Wert ändern. Ich gehe auch davon aus, dass mit "100 oder 1000", was du meintest, "viele und viele" gemeint waren, denn ehrlich gesagt, 1000 Floats sind einfach nicht viele Daten.

    Wenn Sie ein wirklich großes Array zu ziehen, wird Ihre Leistungsgrenze schließlich von looping über das Array kommen. Ihre Leistungsverbesserung wird dann reduzieren, wie viel von dem Array Sie durchlaufen. Hier kommen die Eigenschaften der Daten ins Spiel.

    Eine Möglichkeit, das Volumen des Ankonstruktion zu reduzieren, ist eine ‚schmutzige Liste‘ zu halten, die wie ein Queue<Int> wirkt. Jedes Mal, wenn sich eine Zelle in Ihrem Array ändert, reihen Sie diesen Array-Index in die Warteschlange ein und markieren ihn als "dreckig". Jedes Mal, wenn die Zeichenmethode erneut aufgerufen wird, wird eine feste Anzahl von Einträgen aus der Liste gelöscht und nur der Teil des gerenderten Bilds aktualisiert, der diesen Einträgen entspricht. Sie müssen wahrscheinlich etwas Skalierung und/oder Kantenglättung durchführen etwas, denn mit so vielen Datenpunkten haben Sie wahrscheinlich mehr Daten als Bildschirmpixel. Die Anzahl der Einträge, die Sie bei einem gegebenen Frame-Update neu zeichnen, sollte durch die gewünschte Framerate begrenzt sein - Sie können dies adaptiv machen, basierend auf einer Metrik, wie lange vorherige Draw-Operationen dauerten und wie tief die Dirty-Liste wird Balance zwischen Bildrate und sichtbarem Datenalter.

    Dies ist besonders geeignet, wenn Sie versuchen, alle Daten auf dem Bildschirm auf einmal zu ziehen. Wenn Sie nur einen Teil der Daten anzeigen (wie in einer scrollbaren Ansicht) und eine gewisse Übereinstimmung zwischen den Array-Positionen und der Fenstergröße besteht, können Sie die Daten "windfen" - berücksichtigen Sie bei jedem Zeichenaufruf nur die Teilmenge der Daten, die sich tatsächlich auf dem Bildschirm befinden. Wenn Sie ein Zoom-Objekt haben, können Sie die beiden Methoden kombinieren - das kann kompliziert werden.

    Wenn die Daten, wie gefenstert werden, dass der Wert in jedem Array-Elemente ist, was bestimmt, ob der Datenpunkt am oder außerhalb des Bildschirms ist, sollten eine sortierte Liste von Paaren verwendet, wo die Sortierschlüssel der Wert sind. Auf diese Weise können Sie die in dieser Situation beschriebene Fensteroptimierung durchführen. Wenn die Fensterung in beiden Dimensionen stattfindet, müssen Sie höchstwahrscheinlich nur die eine oder andere Optimierung durchführen, aber es gibt zweidimensionale Bereichsabfragestrukturen, die Ihnen dies ebenfalls geben können.

    Lassen Sie uns sagen, dass meine Annahme um eine feste Datengröße falsch war; Stattdessen fügen Sie am Ende der Liste Daten hinzu, aber vorhandene Datenpunkte ändern sich nicht. In diesem Fall ist es wahrscheinlich besser, wenn Sie eine Queue-ähnliche Struktur verwenden, bei der alte Datenpunkte und nicht ein Array gelöscht werden, da ein wachsendes Array dazu führt, dass die Anwendung unnötigerweise stottert.

    In diesem Fall besteht Ihre Optimierung darin, in einen Puffer vorzuzeichnen, der Ihrer Warteschlange folgt - wenn neue Elemente in die Warteschlange eintreten, verschieben Sie den ganzen Puffer nach links und zeichnen nur die Region mit den neuen Elementen.

    Wenn es die/rate/der Dateneingabe ist, verwenden Sie eine Warteschlangenstruktur und überspringen Sie Elemente - entweder reduzieren Sie sie, wenn sie zur Warteschlange hinzugefügt werden, speichern/zeichnen jedes Element .

    Wenn es stattdessen der Renderprozess ist, der all Ihre Zeit in Anspruch nimmt, ziehen Sie in Erwägung, auf einem Hintergrund-Thread zu rendern und das gerenderte Bild zu speichern. Auf diese Weise können Sie so viel Zeit aufwenden, wie Sie möchten - die Framerate innerhalb des Diagramms selbst wird jedoch nicht die allgemeine Reaktionsfähigkeit Ihrer Anwendung beeinträchtigen.

    0

    Was ich in einer ähnlichen Situation getan habe, ist eine benutzerdefinierte Klasse zu erstellen, nennen wir es "MyView", das View erweitert und es zu meinem Layout-XML hinzufügt.

    public class MyView extends View { 
        ... 
    } 
    

    In Layout:

    <?xml version="1.0" encoding="utf-8"?> 
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
        android:orientation="vertical" 
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent"> 
        <com.yadayada.MyView 
        android:id="@+id/paintme" 
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent" 
        /> 
    </LinearLayout> 
    

    Innerhalb MyView, Überschreibung Methode "OnDraw (Canvas canv)". onDraw erhält eine Leinwand, auf der Sie zeichnen können. Holen Sie in onDraw ein Paint-Objekt neu Paint() und richten Sie es nach Belieben ein. Dann können Sie alle Canvas-Zeichenfunktion verwenden, z. B. DrawLine, DrawPath, DrawBitmap, DrawText und Tonnen mehr.

    Was die Leistung betrifft, schlage ich vor, dass Sie die zugrunde liegenden Daten im Stapel ändern und anschließend die Ansicht ungültig machen. Ich denke du musst mit vollen Remis leben. Aber wenn ein Mensch es beobachtet, ist die Aktualisierung von mehr als jeder Sekunde wahrscheinlich nicht profitabel. Die Canvas-Zeichenmethoden sind unglaublich schnell.

    +0

    Wie ich geschrieben habe, weiß ich, wie man ein Diagramm mit statischen Daten zeichnet. Das Problem besteht darin, dynamische Daten zu plotten. – sebap123

    +0

    Nicht sicher, was du meinst. Wenn sich Ihre Daten in einem Array oder einer Datenbank befinden, ändern Sie sie einfach dort und zeichnen Sie sie neu. Wenn Ihr Thread die Daten ändert, ist die Darstellung * * "dynamisch". Oder versuchen Sie, einen Echtzeit-Videoanimationseffekt zu erzielen? – DontPanic

    +0

    Wenn Sie darüber hinaus Bedenken hinsichtlich der Verwaltung von Neuzeichnungen haben, würde ich vorschlagen, dass Ihr Aktualisierungsthread bei jeder Änderung ein Flag "Neuzeichnen erforderlich" setzt. Starten Sie einen Timer in Ihrer App und überprüfen Sie das Flag und führen Sie einen ungültigen Code aus, falls gesetzt. Auf diese Weise erhalten Sie zeitnahe Updates und keine Lawine von Redraws. – DontPanic

    0

    Jetzt würde ich Ihnen vorschlagen, die GraphView Library. Es ist Open Source, mach dir keine Sorgen um die Lizenz und es ist auch nicht so groß (< 64kB). Sie können die erforderlichen Dateien aufräumen, wenn Sie möchten.

    Aus den amtlichen Proben:

    public class RealtimeUpdates extends Fragment { 
        private final Handler mHandler = new Handler(); 
        private Runnable mTimer1; 
        private Runnable mTimer2; 
        private LineGraphSeries<DataPoint> mSeries1; 
        private LineGraphSeries<DataPoint> mSeries2; 
        private double graph2LastXValue = 5d; 
    
        @Override 
        public View onCreateView(LayoutInflater inflater, ViewGroup container, 
              Bundle savedInstanceState) { 
         View rootView = inflater.inflate(R.layout.fragment_main2, container, false); 
    
         GraphView graph = (GraphView) rootView.findViewById(R.id.graph); 
         mSeries1 = new LineGraphSeries<DataPoint>(generateData()); 
         graph.addSeries(mSeries1); 
    
         GraphView graph2 = (GraphView) rootView.findViewById(R.id.graph2); 
         mSeries2 = new LineGraphSeries<DataPoint>(); 
         graph2.addSeries(mSeries2); 
         graph2.getViewport().setXAxisBoundsManual(true); 
         graph2.getViewport().setMinX(0); 
         graph2.getViewport().setMaxX(40); 
    
         return rootView; 
        } 
    
        @Override 
        public void onAttach(Activity activity) { 
         super.onAttach(activity); 
         ((MainActivity) activity).onSectionAttached(
           getArguments().getInt(MainActivity.ARG_SECTION_NUMBER)); 
        } 
    
        @Override 
        public void onResume() { 
         super.onResume(); 
         mTimer1 = new Runnable() { 
          @Override 
          public void run() { 
           mSeries1.resetData(generateData()); 
           mHandler.postDelayed(this, 300); 
          } 
         }; 
         mHandler.postDelayed(mTimer1, 300); 
    
         mTimer2 = new Runnable() { 
          @Override 
          public void run() { 
           graph2LastXValue += 1d; 
           mSeries2.appendData(new DataPoint(graph2LastXValue, getRandom()), true, 40); 
           mHandler.postDelayed(this, 200); 
          } 
         }; 
         mHandler.postDelayed(mTimer2, 1000); 
        } 
    
        @Override 
        public void onPause() { 
         mHandler.removeCallbacks(mTimer1); 
         mHandler.removeCallbacks(mTimer2); 
         super.onPause(); 
        } 
    
        private DataPoint[] generateData() { 
         int count = 30; 
         DataPoint[] values = new DataPoint[count]; 
         for (int i=0; i<count; i++) { 
          double x = i; 
          double f = mRand.nextDouble()*0.15+0.3; 
          double y = Math.sin(i*f+2) + mRand.nextDouble()*0.3; 
          DataPoint v = new DataPoint(x, y); 
          values[i] = v; 
         } 
         return values; 
        } 
    
        double mLastRandom = 2; 
        Random mRand = new Random(); 
        private double getRandom() { 
         return mLastRandom += mRand.nextDouble()*0.5 - 0.25; 
        } 
    } 
    
    +0

    Nun .. Ich kämpfe bereits mit 'GraphView', aber es sieht so aus als ob es einen Fehler gibt - http://Stackoverflow.com/questions/30290575/android-graphview-project-get-freeze-with-real-time-updates – sebap123

    +0

    Ich habe so etwas noch nicht erlebt. Behalte einen festen Puffer, wie im Balanduino-Projekt, und sowas wird nicht passieren. Ich glaube, er hat unendlich viele Daten hinzugefügt und nicht gelöscht. Stoppen Sie auch, die Benutzeroberfläche zu aktualisieren, wenn die App in den Pause-Modus wechselt oder ein Fehler auftritt. –

    +0

    Eigentlich erinnere ich mich, dass ich das Telefon an AC angeschlossen ließ und die Möglichkeit hatte, den Bildschirm während des Aufladens durch die Nacht an zu lassen und es funktionierte immer noch am Morgen. Aber das war früher und benutzte eine ältere Bibliothek, damals war es 3,4 oder so. Ich habe meinen Code nie aktualisiert, weil es für ein Schulprojekt war ... sobald du die Prüfungen bestanden hast, interessiert dich sowas nicht mehr :))) –

    0

    Wenn Sie bereits eine Ansicht th haben Wenn Sie statische Daten ziehen, sind Sie nahe an Ihrem Ziel. Das einzige, was Sie dann tun müssen, ist:

    1) Extrahieren Sie die Logik, die Daten 2) Extrahieren Sie die Logik abruft, die diese Daten auf Bildschirm zieht 3) Innerhalb der OnDraw() -Methode erste Anruf 1) - dann rufe 2) - dann rufe invalidate() am Ende deiner onDraw() - Methode auf - da dies eine neue Zeichnung auslöst und sich die Ansicht mit den neuen Daten aktualisiert.

    0

    Ich bin nicht sicher, was bei 100 oder 1000 Punkte

    nichts passieren wird, brauchen Sie nicht zu kümmern. Es gibt bereits eine Menge Punkte, die jedes Mal, wenn eine Aktivität auf dem Bildschirm ist, geplottet werden.

    Der erste Gedanke war, einfach einen neuen Punkt hinzuzufügen und zu zeichnen. Füge noch einen und noch einmal hinzu.

    Dies ist der Weg zu gehen, ich fühle. Sie möchten vielleicht einen systematischen Ansatz mit diesem:

    1. überprüfen, ob das Diagramm auf dem Bildschirm oder auf dem Bildschirm angezeigt wird.
    2. Wenn sie auf dem Bildschirm angezeigt werden, fügen Sie sie einfach zu Ihrem Array hinzu und das sollte gut sein.
    3. Wenn die Daten nicht auf dem Bildschirm angezeigt werden, nehmen Sie entsprechende Koordinatenberechnungen unter Berücksichtigung der folgenden Punkte vor: Entfernen Sie das erste Element aus dem Array, fügen Sie das neue Element in das Array ein und zeichnen Sie es neu.

    Nach dieser postinvalidate auf Ihrer Sicht.

    Ich bin das Hinzufügen neuer Punkt, fragen Ansicht selbst ungültig zu machen, aber immer noch einige Punkte nicht gezeichnet.

    Wahrscheinlich gehen Ihre Punkte vom Bildschirm. Überprüfen Sie.

    In diesem Fall kann es auch schwierig sein, einige Warteschlangen zu verwenden, da der Befehl onDraw() wieder von vorne beginnt, so dass die Anzahl der Warteschlangenelemente nur zunimmt.

    Dies sollte kein Problem, da die Punkte auf dem Bildschirm sein wird, begrenzt werden, so wird die Warteschlange nur so viele Punkte halten, wie frühere Punkte werden gelöscht.

    Hoffe dieser Ansatz hilft.

    2

    Dies sollte den Trick tun.

    import android.content.Context; 
    import android.graphics.Canvas; 
    import android.graphics.Paint; 
    import android.os.Bundle; 
    import android.support.v4.view.ViewCompat; 
    import android.support.v7.app.AppCompatActivity; 
    import android.util.AttributeSet; 
    import android.view.View; 
    
    import java.io.Serializable; 
    
    
    public class MainActivity 
        extends AppCompatActivity 
    { 
        private static final String STATE_PLOT = "statePlot"; 
    
        private MockDataGenerator mMockDataGenerator; 
        private Plot mPlot; 
    
    
        @Override 
        protected void onCreate(Bundle savedInstanceState) 
        { 
         super.onCreate(savedInstanceState); 
    
         if(savedInstanceState == null){ 
          mPlot = new Plot(100, -1.5f, 1.5f); 
         }else{ 
          mPlot = (Plot) savedInstanceState.getSerializable(STATE_PLOT); 
         } 
    
         PlotView plotView = new PlotView(this); 
         plotView.setPlot(mPlot); 
         setContentView(plotView); 
        } 
    
        @Override 
        protected void onSaveInstanceState(Bundle outState) 
        { 
         super.onSaveInstanceState(outState); 
         outState.putSerializable(STATE_PLOT, mPlot); 
        } 
    
        @Override 
        protected void onResume() 
        { 
         super.onResume(); 
         mMockDataGenerator = new MockDataGenerator(mPlot); 
         mMockDataGenerator.start(); 
        } 
    
        @Override 
        protected void onPause() 
        { 
         super.onPause(); 
         mMockDataGenerator.quit(); 
        } 
    
    
        public static class MockDataGenerator 
         extends Thread 
        { 
         private final Plot mPlot; 
    
    
         public MockDataGenerator(Plot plot) 
         { 
          super(MockDataGenerator.class.getSimpleName()); 
          mPlot = plot; 
         } 
    
         @Override 
         public void run() 
         { 
          try{ 
           float val = 0; 
           while(!isInterrupted()){ 
            mPlot.add((float) Math.sin(val += 0.16f)); 
            Thread.sleep(1000/30); 
           } 
          } 
          catch(InterruptedException e){ 
           // 
          } 
         } 
    
         public void quit() 
         { 
          try{ 
           interrupt(); 
           join(); 
          } 
          catch(InterruptedException e){ 
           // 
          } 
         } 
        } 
    
        public static class PlotView extends View 
         implements Plot.OnPlotDataChanged 
        { 
         private Paint mLinePaint; 
         private Plot mPlot; 
    
    
         public PlotView(Context context) 
         { 
          this(context, null); 
         } 
    
         public PlotView(Context context, AttributeSet attrs) 
         { 
          super(context, attrs); 
          mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 
          mLinePaint.setStyle(Paint.Style.STROKE); 
          mLinePaint.setStrokeJoin(Paint.Join.ROUND); 
          mLinePaint.setStrokeCap(Paint.Cap.ROUND); 
          mLinePaint.setStrokeWidth(context.getResources() 
           .getDisplayMetrics().density * 2.0f); 
          mLinePaint.setColor(0xFF568607); 
          setBackgroundColor(0xFF8DBF45); 
         } 
    
         public void setPlot(Plot plot) 
         { 
          if(mPlot != null){ 
           mPlot.setOnPlotDataChanged(null); 
          } 
          mPlot = plot; 
          if(plot != null){ 
           plot.setOnPlotDataChanged(this); 
          } 
          onPlotDataChanged(); 
         } 
    
         public Plot getPlot() 
         { 
          return mPlot; 
         } 
    
         public Paint getLinePaint() 
         { 
          return mLinePaint; 
         } 
    
         @Override 
         public void onPlotDataChanged() 
         { 
          ViewCompat.postInvalidateOnAnimation(this); 
         } 
    
         @Override 
         protected void onDraw(Canvas canvas) 
         { 
          super.onDraw(canvas); 
    
          final Plot plot = mPlot; 
          if(plot == null){ 
           return; 
          } 
    
          final int height = getHeight(); 
          final float[] data = plot.getData(); 
          final float unitHeight = height/plot.getRange(); 
          final float midHeight = height/2.0f; 
          final float unitWidth = (float) getWidth()/data.length; 
    
          float lastX = -unitWidth, lastY = 0, currentX, currentY; 
          for(int i = 0; i < data.length; i++){ 
           currentX = lastX + unitWidth; 
           currentY = unitHeight * data[i] + midHeight; 
           canvas.drawLine(lastX, lastY, currentX, currentY, mLinePaint); 
           lastX = currentX; 
           lastY = currentY; 
          } 
         } 
        } 
    
    
        public static class Plot 
         implements Serializable 
        { 
         private final float[] mData; 
         private final float mMin; 
         private final float mMax; 
    
         private transient OnPlotDataChanged mOnPlotDataChanged; 
    
    
         public Plot(int size, float min, float max) 
         { 
          mData = new float[size]; 
          mMin = min; 
          mMax = max; 
         } 
    
         public void setOnPlotDataChanged(OnPlotDataChanged onPlotDataChanged) 
         { 
          mOnPlotDataChanged = onPlotDataChanged; 
         } 
    
         public void add(float value) 
         { 
          System.arraycopy(mData, 1, mData, 0, mData.length - 1); 
          mData[mData.length - 1] = value; 
          if(mOnPlotDataChanged != null){ 
           mOnPlotDataChanged.onPlotDataChanged(); 
          } 
         } 
    
         public float[] getData() 
         { 
          return mData; 
         } 
    
         public float getMin() 
         { 
          return mMin; 
         } 
    
         public float getMax() 
         { 
          return mMax; 
         } 
    
         public float getRange() 
         { 
          return (mMax - mMin); 
         } 
    
         public interface OnPlotDataChanged 
         { 
          void onPlotDataChanged(); 
         } 
        } 
    } 
    
    Verwandte Themen