English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Android Custom View zur Implementierung des Pendulum-Effect ProgressBars PendulumView

Ich habe online ein iOS-Componente PendulumView gesehen, das den Animationseffekt einer Pendelupe implementiert. Da die nativen Fortschrittsbalken wirklich nicht schön aussehen, möchte ich eine solche Eigenschaft durch einen eigenen View implementieren, die später auch für den Fortschrittsbalken auf Ladeseiten verwendet werden kann. 

Nichts zu langes Geplänkel, zeige erstmal das Ergebnisbild

 

Die schwarze Kante am Boden wurde versehentlich während der Aufzeichnung hinzugefügt und kann ignoriert werden. 

Da es sich um einen eigenen View handelt, verfolgen wir den Standardprozess. Schritt 1: Eigenschaften definieren 

Eigenschaften definieren 

Eigenschaftendatei erstellen 

Im Android-Projekt res->values-Verzeichnis einen neuen attrs.xml-Datei erstellen, der Inhalt der Datei ist wie folgt:

 <?xml version="1.0" encoding="utf-8"?>
<resources>
 <declare-styleable name="PendulumView">
  <attr name="globeNum" format="integer"/>
  <attr name="globeColor" format="color"/>
  <attr name="globeRadius" format="dimension"/>
  <attr name="swingRadius" format="dimension"/>
 </declare-styleable>
</resources>

Darin declare-Die name-Eigenschaft von styleable wird verwendet, um auf die Eigenschaftendatei im Code zu verweisen. Der Name der Eigenschaft wird in der Regel in der Klasse unseres eigenen Views geschrieben und ist daher sehr intuitiv.

Mit styleale kann das System viele Konstanten (int-Array, Indexkonstanten) usw. für uns schreiben und unsere Entwicklungsarbeit vereinfachen. Zum Beispiel werden im folgenden Code verwendete Elemente wie R.styleable.PendulumView_golbeNum automatisch generiert. 

Die Eigenschaft globeNum stellt die Anzahl der Kugeln dar, globeColor die Farbe der Kugeln, globeRadius den Radius der Kugeln und swingRadius den Schwingungsradius 

Lesen Sie Attributwerte 

Lesen Sie Attributwerte im Konstruktor eines benutzerdefinierten Views 

Man kann Attribute auch über AttributeSet abrufen, aber wenn der Attributwert ein Referenztyp ist, erhält man nur die ID, und es muss weiterhin durch Parsing der ID der tatsächliche Attributwert erhalten werden. TypedArray hilft uns direkt dabei. 

public PendulumView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    //Verwenden Sie TypedArray, um benutzerdefinierte Attributwerte zu lesen
    TypedArray ta = context.getResources().obtainAttributes(attrs, R.styleable.PendulumView);
    int count = ta.getIndexCount();
    for (int i = 0; i < count; i++) {
      int attr = ta.getIndex(i);
      switch (attr) {
        case R.styleable.PendulumView_globeNum:
          mGlobeNum = ta.getInt(attr, 5);
          break;
        case R.styleable.PendulumView_globeRadius:
          mGlobeRadius = ta.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 16, getResources().getDisplayMetrics()));
          break;
        case R.styleable.PendulumView_globeColor:
          mGlobeColor = ta.getColor(attr, Color.BLUE);
          break;
        case R.styleable.PendulumView_swingRadius:
          mSwingRadius = ta.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 16, getResources().getDisplayMetrics()));
          break;
      }
    }
    ta.recycle(); //避免下次读取时出现问题
    mPaint = new Paint();
    mPaint.setColor(mGlobeColor);
  }

重写OnMeasure()方法 

@Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    //高度为小球半径+摆动半径
    int height = mGlobeRadius + mSwingRadius;
    //宽度为2*摆动半径+(小球数量-1)*小球直径
    int width = mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1) + mSwingRadius;
    //如果测量模式为EXACTLY,则直接使用推荐值,如不为EXACTLY(一般处理wrap_content情况),使用自己计算的宽高
    setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize : width, (heightMode == MeasureSpec.EXACTLY) ? heightSize : height);
  }

其中
 int height = mGlobeRadius + mSwingRadius;
<pre name="code" class="java">int width = mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1) + mSwingRadius;
用于处理测量模式为AT_MOST的情况,一般是自定义View的宽高设置为了wrap_content,此时通过小球的数量,半径,摆动的半径等计算View的宽高,如下图: 

以小球个数5例如,View的大小为下图红色矩形区域 

Überschreiben Sie die Methode onDraw() 

@Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //Zeichnen Sie alle Kugeln außer den beiden äußeren Kugeln
    for (int i = 0; i < mGlobeNum - 2; i++) {
      canvas.drawCircle(mSwingRadius + (i + 1) * 2 * mGlobeRadius, mSwingRadius, mGlobeRadius, mPaint);
    }
    if (mLeftPoint == null || mRightPoint == null) {
      //Initialisieren Sie die Koordinaten der beiden äußeren Kugeln
      mLeftPoint = new Point(mSwingRadius, mSwingRadius);
      mRightPoint = new Point(mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1), mSwingRadius);
      //Starten Sie die Pendelanimation
      startPendulumAnimation();
    }
    //Zeichnen Sie die beiden äußeren Kugeln
    canvas.drawCircle(mLeftPoint.x, mLeftPoint.y, mGlobeRadius, mPaint);
    canvas.drawCircle(mRightPoint.x, mRightPoint.y, mGlobeRadius, mPaint);
  }

Die Methode onDraw() ist der Schlüssel für die Erstellung eines benutzerdefinierten Views, da hier der Anzeigeeffekt des Views gezeichnet wird. Der Code zeichnet zunächst alle Kugeln außer den beiden äußeren Kugeln, überprüft dann die Koordinatenwerte der beiden äußeren Kugeln. Wenn es sich um die erste Zeichnung handelt und die Koordinatenwerte leer sind, werden die Koordinaten der beiden äußeren Kugeln initialisiert und die Animation gestartet. Schließlich werden die x- und y-Werte von mLeftPoint und mRightPoint verwendet, um die beiden äußeren Kugeln zu zeichnen. 

mLeftPoint und mRightPoint sind Objekte der Klasse android.graphics.Point, die nur verwendet werden, um die x- und y-Koordinateninformationen der beiden kleinen Kugeln links und rechts zu speichern. 

Verwenden Sie die Eigenschaftsanimation 

public void startPendulumAnimation() {
    //Verwenden Sie die Eigenschaftsanimation
    final ValueAnimator anim = ValueAnimator.ofObject(new TypeEvaluator() {
      @Override
      public Object evaluate(float fraction, Object startValue, Object endValue) {
        //Der Parameter fraction wird verwendet, um den Fortschritt der Animation anzuzeigen, und wir berechnen den aktuellen Wert der Animation daran
        double angle = Math.toRadians(90 * fraction);
        int x = (int) ((mSwingRadius - mGlobeRadius) * Math.sin(angle));
        int y = (int) ((mSwingRadius - mGlobeRadius) * Math.cos(angle));
        Point point = new Point(x, y);
        return point;
      }
    }, new Point(), new Point());
    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        Point point = (Point) animation.getAnimatedValue();
        //erhalten Sie den aktuellen Wert fraction
        float fraction = anim.getAnimatedFraction();
        //Prüfen Sie, ob fraction zuerst abnimmt und dann zunimmt, d.h. ob der Ball sich in einer bevorstehenden Bewegung nach oben befindet
        //Wechsel den Ball bei jeder bevorstehenden Bewegung nach oben
        if (lastSlope && fraction > mLastFraction) {
          isNext = !isNext;
        }
        //durch ständige Änderung der x- und y-Koordinatenwerte der linken und rechten Kugeln wird der Animationseffekt erreicht
        //isNext wird verwendet, um zu bestimmen, ob der linke oder der rechte Ball schwingen soll
        if (isNext) {
          //wenn der linke Ball schwingt, wird der rechte Ball in die Ausgangsposition versetzt
          mRightPoint.x = mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1);
          mRightPoint.y = mSwingRadius;
          mLeftPoint.x = mSwingRadius - point.x;
          mLeftPoint.y = mGlobeRadius + point.y;
        } else {
          //wenn der rechte Ball schwingt, wird der linke Ball in die Ausgangsposition versetzt
          mLeftPoint.x = mSwingRadius;
          mRightPoint.y = mSwingRadius;
          mRightPoint.x = mSwingRadius + (mGlobeNum - 1) * mGlobeRadius * 2 + point.x;
          mRightPoint.y = mGlobeRadius + point.y;
        }
        invalidate();
        lastSlope = fraction < mLastFraction;
        mLastFraction = fraction;
      }
    });
    //den Wiedergabemodus auf unendlich wiederholen einstellen
    anim.setRepeatCount(ValueAnimator.INFINITE);
    //den Wiedergabemodus auf Abspielen im umgekehrten Uhrzeigersinn einstellen
    anim.setRepeatMode(ValueAnimator.REVERSE);
    anim.setDuration(200);
    //Setzen Sie den Easing-Interpolator, um die Geschwindigkeit der Änderung der Animation zu steuern
    anim.setInterpolator(new DecelerateInterpolator());
    anim.start();
  }

 dabei wird ValueAnimator.ofObject verwendet, um mit Point-Objekten zu arbeiten, was bildhafter und spezifischer ist. Außerdem wird durch die Verwendung von ofObject der benutzerdefinierte TypeEvaluator-Objekt verwendet, wodurch der Wert fraction erhalten wird, der eine Dezimalzahl von 0-1veränderliche Dezimalzahl. Daher haben die beiden letzten Parameter startValue (new Point()) und endValue (new Point()) keine tatsächliche Bedeutung und können auch weggelassen werden. Der Hauptgrund für ihre Aufnahme ist die Klarheit der Erklärung. Auf gleiche Weise kann auch ValueAnimator.ofFloat(0f, 1f) eine Dezimalzahl von 0-1veränderliche Dezimalzahl.

     final ValueAnimator anim = ValueAnimator.ofObject(new TypeEvaluator() {
      @Override
      public Object evaluate(float fraction, Object startValue, Object endValue) {
        //Der Parameter fraction wird verwendet, um den Fortschritt der Animation anzuzeigen, und wir berechnen den aktuellen Wert der Animation daran
        double angle = Math.toRadians(90 * fraction);
        int x = (int) ((mSwingRadius - mGlobeRadius) * Math.sin(angle));
        int y = (int) ((mSwingRadius - mGlobeRadius) * Math.cos(angle));
        Point point = new Point(x, y);
        return point;
      }
    }, new Point(), new Point());

Durch fraction berechnen wir den Winkeländerungswert des Balls während der Schwingung,0-90 Grad

 

mSwingRadius-mGlobeRadius stellt die Länge der grünen Linie im Bild dar,Der Bewegungspfad des Balls ist ein mit (mSwingRadius-mGlobeRadius) als Radius der Kurve, Der sich ändernde X-Wert ist (mSwingRadius-mGlobeRadius)*sin(angle),Der sich ändernde y-Wert ist (mSwingRadius-mGlobeRadius)*cos(angle) 

Der tatsächliche Mittelpunkt des entsprechenden Balls ist (mSwingRadius-x,mGlobeRadius+y) 

Der Bewegungspfad des rechten Balls ist ähnlich wie der des linken, nur die Richtung ist unterschiedlich. Der tatsächliche Mittelpunkt des rechten Balls (mSwingRadius + (mGlobeNum - 1) * mGlobeRadius * 2 + x,mGlobeRadius+y) 

Die y-Koordinaten der beiden linken und rechten Bälle sind gleich, nur die x-Koordinaten unterscheiden sich. 

        float fraction = anim.getAnimatedFraction();
        //Prüfen Sie, ob fraction zuerst abnimmt und dann zunimmt, d.h. ob der Ball sich in einer bevorstehenden Bewegung nach oben befindet
        //Wechsel den Ball bei jeder bevorstehenden Bewegung nach oben
        if (lastSlope && fraction > mLastFraction) {
          isNext = !isNext;
        }
        //Die letzte fraction wird aufgezeichnet, ob sie ständig abnimmt
        lastSlope = fraction < mLastFraction;
        //Die letzte fraction wird aufgezeichnet
        mLastFraction = fraction;

 Diese zwei Zeilen Code dienen dazu, zu berechnen, wann der Ball die Bewegung wechseln soll. Diese Animation ist auf Wiedergabe in der Reihenfolge eingestellt und der Wiedergabemodus ist umgekehrt. Daher ist ein Zyklus der Animation der Prozess, bei dem der Ball hochgeworfen und wieder herunterfällt. Während dieses Prozesses ändert sich der Wert von fraction von 0 auf1wiederum}1auf 0. Dann ist der Anfang eines neuen Zyklus der Animation der Moment, in dem der Ball hochgeworfen wird. In diesem Moment wird der bewegte Ball gewechselt, um die Animation zu erzeugen, bei der der linke Ball nach dem Fallen der rechte Ball hochgeworfen wird und umgekehrt. 

Wie kann man diesen Zeitpunkt erfassen? 

Wenn der Ball hochgeworfen wird, nimmt der Wert von fraction kontinuierlich zu, und wenn der Ball fällt, nimmt der Wert von fraction kontinuierlich ab. Der Moment, in dem der Ball hochgeworfen wird, ist der Moment, in dem fraction von kontinuierlich abnehmend zu kontinuierlich zunehmend wird. Der Code speichert, ob der letzte Wert von fraction kontinuierlich abnimmt, und vergleicht ihn mit dem aktuellen Wert von fraction, ob er kontinuierlich zunimmt. Wenn beide Bedingungen erfüllt sind, wird der bewegte Ball gewechselt. 

    anim.setDuration(200);
    //Setzen Sie den Easing-Interpolator, um die Geschwindigkeit der Änderung der Animation zu steuern
    anim.setInterpolator(new DecelerateInterpolator());
    anim.start();

Setzen Sie die Dauer der Animation auf200 Millisekunden, der Leser kann die Geschwindigkeit des Schwingens des Balls ändern, indem er diesen Wert ändert.

Setzen Sie den Easing-Interpolator für die Animation, da der Ballwurf ein allmählich verlangsamter Prozess ist und der Fallen des Balls ein allmählich beschleunigter Prozess ist, wird der DecelerateInterpolator für den Verlangsamungseffekt verwendet und der Beschleunigungseffekt im umgekehrten Abspielmodus. 

Mit der Animation, dem benutzerdefinierten Fortschrittsbalken mit Schaukelschwung, wurde die Animation gestartet! Schnell ausführen und die Ergebnisse ansehen!

Das ist der gesamte Inhalt dieses Artikels. Wir hoffen, dass er Ihnen bei Ihrem Lernen hilft und dass Sie die Anleitung für Rufen stark unterstützen.

Erklärung: Der Inhalt dieses Artikels wurde aus dem Internet übernommen und gehört dem Urheberrecht des Urhebers. Der Inhalt wurde von Internetnutzern freiwillig beigesteuert und hochgeladen. Diese Website besitzt keine Eigentumsrechte und hat den Inhalt nicht manuell bearbeitet. Sie übernimmt auch keine rechtlichen Verantwortlichkeiten. Wenn Sie urheberrechtlich anstößige Inhalte entdecken, freuen wir uns über eine E-Mail an: notice#oldtoolbag.com (Bitte ersetzen Sie # durch @, wenn Sie eine Meldung senden, und fügen Sie relevante Beweise bei. Bei nachgewiesener Täuschung wird diese Website die beanstandeten urheberrechtlichen Inhalte sofort löschen.)

Mag sein