モバイル端末における光のスイープモーション効果の応用実践

ここに画像の説明を挿入

著者 | セブン

ガイド

モバイル インターネットの急速な発展に伴い、業界では創造的で興味深いインタラクティブなエクスペリエンスが多数登場しています。スイープ モーション エフェクトは、興味深い読み込みモーション エフェクトの 1 つで、一般的なスイープ モーション エフェクトには、スケルトン スクリーンのスイープやロゴのスイープなどがあります。この記事では、これら 2 つの包括的な効果の原理、これら 2 つの包括的な効果を実現する方法、iOS と Andoird のデュアルエンド実装の違いについて詳しく説明します。

全文は10,549ワード、予想読了時間は27分です。

01 はじめに

モバイル端末の一般的なローディング アニメーション効果として、スイープ アニメーション効果は、従来の回転ローディング アニメーションよりも優れた視覚的および感覚的なエクスペリエンスを人々に与えることができます。主な特徴は、光の効果が時間とともに広がり、文字やパターンが色で満たされているように見えることです。

筆者はこれまでスケルトンスクリーンスイープとクマの足スイープロードを続けて実行してきましたが、この記事ではこの2つのスイープダイナミックエフェクトの実現と技術的な違いをiOSとAndroidそれぞれの観点から紹介します。

写真

△クマの足跡ライトスイープモーションエフェクト

02 スケルトンスクリーンスイープエフェクト

スケルトン画面はインターフェース読み込み時のトランジションエフェクトです。ページ データがロードされる前に、まずユーザーにページの一般的な構造が表示され、インターフェイス データを取得した後、実際のページ コンテンツがレンダリングされて置き換えられます。このテクノロジーにより、ユーザーの不安が軽減され、インターフェイスの読み込みプロセスが自然かつスムーズになり、ユーザー エクスペリエンスが向上します。記事一覧や動的一覧ページなど、比較的定型的な一覧ページによく使われます。ここでは、支払いのハーフスクリーン パネルを例として取り上げます。スケルトン ダイアグラム全体に広がる光の効果がわかります。

[外部リンク画像の転送に失敗しました。ソース サイトには盗難防止リンク メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-lyHmjYNh-1688436549789)(https://oscimg.oschina.net/oscnet/) up-36f4e302d644643978799d98f2abbe5e10b.gif)]

△スケルトンスクリーンスキャンライト

2.1 スケルトン画面のスイープ原理の解析

スケルトンスクリーンの光掃引シーンは背景が不透明であるため比較的単純であり、光掃引効果はスケルトン画像上にマスクビューを光のブロックとして重ね合わせ、マスクを移動することで実現できます。

ビュー階層は全体で2層に分かれており、下層がカスタムビューのスケルトン部分、上層がグラデーション透明マスクとなります。このうち、スケルトン図部分は従来のリストによって実装されますが、ここでは繰り返しません。また、勾配透明マスクはスイープライトとして使用され、図を切り取るかコードによって実現できます。コードの実装と比較して、マスク カット イメージはパッケージのボリュームの一部を増加させるため、スケルトン イメージと同じサイズのビューをカスタマイズし、スケルトン イメージにオーバーレイして、徐々に透明になるように設定することを選択できます。左から右へ。

また、変位アニメーションは、マスクビューをスケルトン図の左側からスケルトン図の右側まで水平方向にxxx時間以内に移動させることで実現できます。

2.2 iOSの実装

Core Animation は AppKit と UIKit の完璧な基盤サポートであり、Cocoa と Cocoa Touch のワークフローにも統合されており、アプリ インターフェイスのレンダリングと構築のための最も基本的なフレームワークです。Core Animation の主な役割には、レンダリング、アニメーションの構築と実装、画面上でさまざまなビジュアル コンテンツをできるだけ早く組み合わせることが含まれます。このコンテンツは独立したレイヤー (特に iOS の CALayer) に分解され、ツリー形状の階層として保存されます。このツリーは、UIKit および iOS アプリケーションの画面に表示されるすべてのものの基礎も形成します。

写真

iOS では、CALayer +View アニメーション メソッド +Transform によって実現できます。レイヤーは UIView の基礎となるレイヤーであり、ビューの描画、アニメーション、境界線、影、その他の視覚効果を担当します。アニメーション部分はViewのクラスメソッドanimateWithDurationを直接利用することができ、水平移動はアニメーションコールバックにビューのTransformプロパティを設定することで実現できます。

グラデーション マスク パーツは、次のコードに従って定義できます。ImageView をマスク ビューとして使用し、CAGradientLayer をそのレイヤーとして設定して、透明なグラデーション効果を実現します。

// 创建自定义视图作为遮罩视图
_lightCover = [[UIImageView alloc] initWithFrame:self.bounds];
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = _lightCover.bounds;
// 渐变色颜色数组
gradientLayer.colors = [NSArray arrayWithObjects:
                    (id)[UIColorFromRGBA(0xFFFFFF, 0) CGColor], 
                    (id)[UIColorFromRGBA(0xFFFFFF, 0.3) CGColor], 
                    (id)[UIColorFromRGBA(0xFFFFFF, 0.5) CGColor], 
                    (id)[UIColorFromRGBA(0xFFFFFF, 0.3) CGColor], 
                    (id)[UIColorFromRGBA(0xFFFFFF, 0) CGColor], nil];
// 渐变的开始点 (不同的起始点可以实现不同位置的渐变,如图)
gradientLayer.startPoint = CGPointMake(0, 0.5f);
// 渐变的结束点
gradientLayer.endPoint = CGPointMake(1, 0.5f);
// 把渐变图层添加到遮罩视图的顶层
[_lightCover.layer insertSublayer:gradientLayer atIndex:0];
// 设置初始位置
_lightCover.transform = CGAffineTransformMakeTranslation(-self.bounds.size.width, 0);

タイマーによるループ変位アニメーション:

// 定时器:动画时间duration + 延迟时间delay = 定时器间隔时间intervalTime
self.lightSweepTimer = [NSTimer scheduledTimerWithTimeInterval:intervalTime target:self selector:@selector(lightSweepAnimation) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.lightSweepTimer forMode:NSRunLoopCommonModes];

アニメーション部分は、View の animateWithDuration メソッドを使用して直接実装されます。

self.lightCover.transform = CGAffineTransformMakeTranslation(-self.bounds.size.width, 0);
[UIView animateWithDuration:duration animations:^{
    self.lightCover.transform = CGAffineTransformMakeTranslation(self.bounds.size.width, 0.f);
} completion:^(BOOL finished) {

}];

タイマーの実行時間は長いため、ロード時に最初にアニメーションを実行できます。

//定时器时间较长,先执行一次动画
[self lightSweepAnimation];

2.3 Androidの実装

Android のレンダリング テクノロジは主に、ビューのレイアウトと描画を処理する View システムに基づいて構築されています。View は、主に独自の描画を担当するコントロールを表し、ViewGroup は主に、それに含まれるサブ View とサブ ViewGroup の管理とレイアウトを担当するコンテナを表します。

写真

これは、Shape+ObjectAnimator をカスタマイズすることで実現できます。Shape は特別な種類の View であり、XML で定義されたタグを通じてカスタム シェイプと関連エフェクトを実現します。関連する属性を通じてさまざまなシェイプを描画し、グラデーション、シャドウ、境界線などのエフェクトをそれらに適用できます。ObjectAnimator は、位置、サイズ、回転、スケーリング、透明度など、アニメーションをサポートするすべてのプロパティに使用できます。アニメーション化するプロパティ名とターゲット値を指定するだけです。

マスク パーツでは、次のようにグラデーションの四角形を定義できます。

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <gradient
        android:startColor="#00ffffff"
        android:centerColor="#7fffffff"
        android:endColor="#00ffffff"
        ></gradient>
</shape>

写真

別のハンドラーを定義して、メインスレッドのビューを更新します。

Handler mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        int what = msg.what;
        if (msgAnimation == what) {
            runAnimation();
        }
        return false;
    }
});

ObjectAnimator と属性 translationX を通じてディスプレイスメント アニメーションを定義します。

private void runAnimation() {
    if (displayWidth == 0) {
        displayWidth = defaultWidth;
    }
    ObjectAnimator translationX = ObjectAnimator.ofFloat(mMoveLight, "translationX", -displayWidth, displayWidth);
    translationX.setDuration(duration);
    translationX.setRepeatCount(0);
    translationX.start();
    translationX.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            sendMsg(msgAnimation, delayTime);
        }
    });
}

遅延後にアニメーションを続行します。

private void sendMsg(int what, int delayTime) {
    checkParent();
    if (mHandler != null) {
        mHandler.sendEmptyMessageDelayed(what, delayTime);
    }
}

読み込みが開始されたら、最初にアニメーションを実行します。

public void startLoading() {
    setVisibility(VISIBLE);
    sendMsg(msgAnimation, 0);
}

03 クマの足跡ライトスイープモーションエフェクト

クマの足をスイープするライトは、主にページが読み込まれるときのトランジション効果として使用され、コンテンツが読み込まれる前に表示されます。通常はページ コンテンツの上部にあり、下部のコンテンツを完全に覆うことはできません。また、日中モード (さまざまなコンテンツの背景色を使用)、夜間モード (日中モードに基づいて、灰色の透明なマスクのレイヤーで覆われます)、ダーク モードおよびさまざまなシーン モードでは、スイープにも影響します。特に日中モードの灰色の背景や夜間モードでは、全体的な光が見えなくなる可能性があります。

写真

△ デイモードの場合は白の背景、デイモード、ナイトモード、ダークモードの場合はグレーの背景

3.1 iOSの実装

クマの足を掃くという複雑なシーンでは、2 層ビューの重ね合わせだけではニーズを満たすことができず、スライダーにはさまざまな異常な状況が発生します (詳細についてはセクション 3.2.1 を参照)。iOS では 3 層構造を使用できます。最下層はスキャンする画像、中央は動く光のブロック、最上層はベース画像に従って描画された中空の層です。3 層のビューは次のとおりです。光とパターンの混合物を形成するために重ね合わされます。

写真

△iOSはマスクによる掃引効果の原理を実現

iOS の CoreAnimation フレームワークは優れており、その View 実装はこの 3 層構造のニーズをまさに満たしています。

写真

  • ビュービュー

  • ビューは、ユーザー インターフェイスの表示と処理に使用される基本的なユーザー インターフェイス要素です。これらは、標準の UI コントロール (UILabel、UIButton など) またはカスタム ビューの場合があります。

  • 各ビューには独自の描画領域があり、他のビューをそのサブビューとして含めることができます。

  • レイヤー レイヤー

  • Layer は、View の基礎となる描画階層の不可欠な部分です。各ビューには、それに関連付けられた Layer オブジェクト (CALayer クラスのインスタンス) があります。

  • レイヤーは、ビューの背景色、境界線、影などを含む、ビューのコンテンツの描画と表示を処理する責任があります。各レイヤーには独自の描画領域があり、ビューの境界に対応します。

  • マスクマスク

  • マスクはレイヤーの可視性を制御するメカニズムです。これは、レイヤーに関連付けることができる透明な画像または形状です。

  • マスクを適用すると、レイヤーのどの領域を表示し、どの領域を非表示にするかを定義できます。

  • マスクは通常、別のレイヤーまたはカスタム イメージから作成され、レイヤーのコンテンツの表示部分を決定します。

iOS は、レイヤーをライトとして使用し、マスクをマスクとして使用して、クマの足のロゴにライトを混合する効果を実現できます。

// loadingView 设置熊掌底图
self.loadingImgView.image = [self lightImg];

// 创建渐变图层
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = CGRectMake(0, 0, loadingWidth, loadingHeight);
// 设置渐变颜色
gradientLayer.colors = @[(__bridge id)[UIColor colorWithWhite:1 alpha:0].CGColor, 
                        (__bridge id)[UIColor colorWithWhite:1 alpha:0.9].CGColor, 
                        (__bridge id)[UIColor colorWithWhite:1 alpha:0.9].CGColor, 
                        (__bridge id)[UIColor colorWithWhite:1 alpha:0].CGColor];
gradientLayer.locations = @[@0.0, @0.49, @0.495, @1.0];
gradientLayer.startPoint = CGPointMake(0, 0.5);
gradientLayer.endPoint = CGPointMake(1, 0.35);
[self.loadingImgView.layer addSublayer:gradientLayer];

// 创建透明遮罩
CALayer *maskLayer = [[CALayer alloc] init];
maskLayer.frame = CGRectMake(0, 0, loadingWidth, loadingHeight);
maskLayer.backgroundColor = [UIColor clearColor].CGColor;
// 设置遮罩内容
maskLayer.contents = (__bridge id _Nullable)([self lightImg].CGImage);
self.loadingImgView.layer.mask = maskLayer;

レイヤーレイヤーの CABasicAnimation を通じて水平方向の変位を実現します。

// 定义基本动画, 控制在x轴方向的位移
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
animation.duration = 2;
// 重复次数1000000次(无限次)
animation.repeatCount = 1000000;
// 动画不会自动反转
animation.autoreverses = false;
animation.fromValue = @(-loadingWidth);
animation.toValue = @(loadingWidth);
// 动画在完成后不会被移除
animation.removedOnCompletion = NO;
// 动画结束后图层保持最后一个状态
animation.fillMode = kCAFillModeForwards;
[self.gradientLayer addAnimation:animation forKey:@"loading_animation_key"];

3.2 Androidの実装

ここでは Android 上の 2 層ビュー オーバーレイを例に挙げますが、さまざまな問題が発生することがわかります。

3.2.1 二重層カスタムビューによるオーバーレイ

カット画像を重ね合わせることで実現するが、デイモード(背景が灰色)ではスライダーが見えず、ダークモードではスライダーが目立つという従来の問題点があった。

写真

△ オーバーレイカット

Shape を重ね合わせることで実現され、スケルトン スクリーン シーンのスイープ エフェクトは問題ありませんが、クマの足をスイープするエフェクトは良くありません。既存の問題は、昼間モードでは白い背景は正常ですが、灰色の背景ではスライダーが表示されない場合です。また、回転角を加えなくてもダークモードでの効果は悪くなく、回転角を重ね合わせるとスライダーの跡がはっきりと確認できます。

写真

△オーバーレイ形状レイヤー

写真

△ オーバーレイ形状レイヤー(回転角度)

LinearGradient を使用して、勾配を含むカスタム グラデーション スイープを実装することもできます。中心となるメソッドは次のとおりです。

// float k = 1f * h / w;
mValueAnimator = ValueAnimator.ofFloat(0f - offset * 2, w + offset * 2);
mValueAnimator.setRepeatCount(repeatCount);
mValueAnimator.setInterpolator(new LinearInterpolator());
mValueAnimator.setDuration(duration);
mValueAnimator.addUpdateListener(animation -> {
    float value = (float) animation.getAnimatedValue();
    LinearGradient mLinearGradient = new LinearGradient(
            value,
            k * value,
            value + offset,
            k * (value + offset),
            colors,
            positions,
            Shader.TileMode.CLAMP
    );
    mPaint.setShader(mLinearGradient);
    invalidate();
});
mValueAnimator.start();

ValueAnimator の更新コールバック内:

  • 値と傾き k に従って 2 つの制御点の座標を計算します。

  • これら 2 つの制御点で定義される線形グラデーション LinearGradient を作成します。

  • ペイントのシェーダーとしてグラデーションを設定します

  • ビューを再描画するには、invalidate() を呼び出します。

ValueAnimator は常に更新されるため、線形グラデーションの 2 つの制御ポイントも常に変化し、その結果、グラデーション アニメーション効果が得られます。

写真

ただし、この方法では、白い背景でのみ効果が向上し、灰色の背景や暗い効果では満足のいく効果が得られず、より明らかなスライダーが表示されます。

3.2.2 キャンバス経由で描画する

Android 上の iOS とは異なり、View 自体が複数のレイヤーを設定できます。スイープ ライトと背景イメージを混合してレンダリングする場合は、マスク レイヤをカスタマイズして 3 層構造にするか、下位レベルの描画を通じて直接処理する必要があります。

では、広がる光と背景画像をどのように混合してレンダリングするのでしょうか? 答えは、PorterDuffXferMode を通じて実現できるということです。

PorterDuffXferMode は PorterDuff.Mode ルールを使用して、描画されたグラフィックスと Canvas 上のグラフィックスを混合し、最後に Canvas を更新して新しいグラフィックスを表示します。PorterDuffXferMode の使い方も非常に簡単で、必要な場合は、paint.setXfermode(PorterDuff.Mode mode) で混合モードを設定します。

PorterDuff.Mode は、CLEAR、SRC、DST、SRC_OVER、DST_OVER、SRC_IN、DST_IN、SRC_OUT、DST_OUT、SRC_ATOP、DST_ATOP、XOR、DARKEN、LIGHTEN、MULTIPLY、SCREEN の 16 のモードに分かれています。

Android は Canvas を使用して View 上にグラフィックを描画します。描画されたグラフィック内のピクセルはソース ピクセル (source、略して src) と呼ばれ、Canvas 内の対応する位置に描画される四角形内のピクセルはデスティネーション ピクセル (destination、略して dst) と呼ばれます。 )。)。Canvas 上の同じ位置にあるソース ピクセルの 4 つの ARGB コンポーネントとターゲット ピクセルの 4 つの ARGB コンポーネントが、Xfermode によって定義されたルールに従って計算されて、最終的な ARGB 値が形成され、最終的な ARGB 値を使用して更新されます。ターゲットピクセルのARGB値。

公式 Web サイトで提供されている図で説明するために、青色のソース ピクセル グラフィックと赤色のターゲット ピクセル グラフィックがあると仮定します。

写真

DST_IN により、交差部分が赤色であるセクターを取得できます。つまり、交差部分はターゲット ピクセルを保持し、交差しない部分はソース ピクセルを破棄します。

写真

このように、まず PorterDuffXfermode で DST_IN のミキシング効果を設定し、LinearGradient でマスクのグラデーション効果を作成します。次に、Canvas と Paint を使用してビットマップを描画およびレンダリングします。最初にマスクされていないビットマップを src として描画し、次にマスクされたビットマップを dst として描画し、その 2 つを組み合わせてスイープ効果を形成し、最後に ValueAnimator を使用してアニメーション効果を実現します。

写真

△図の明るい部分がdst

まず、勾配のあるグラデーション マスク ビットマップを作成します。

mMaskBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(mMaskBitmap);
// 可以通过参数控制getGradientColors 的值,在不同模式下为不同的渐变颜色
Shader gradient = new LinearGradient(
                        0, 0,
                        width, 0,
                        getGradientColors(),
                        getGradientPositions(),
                        Shader.TileMode.REPEAT);
canvas.rotate(mTilt, width / 2, height / 2);
Paint paint = new Paint();
paint.setShader(gradient);
// 适度增大矩形区域,适配倾斜
int padding = (int) (Math.sqrt(2) * Math.max(width, height)) / 2;
canvas.drawRect(-padding, -padding, width + padding, height + padding, paint);

次に、dispatchDraw メソッドでソース ビットマップとターゲット ビットマップを順番に描画します。

// 先绘制一个未经过遮罩处理的位图,作为 src
drawUnmasked(new Canvas(unmaskBitmap));

Canvas unmaskRenderCanvas = (new Canvas(maskBitmap));
unmaskRenderCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
super.dispatchDraw(unmaskRenderCanvas);
canvas.drawBitmap(unmaskBitmap, 0, 0, mAlphaPaint);

// 再绘制一个经过遮罩处理的位图,作为dst
Canvas maskRenderCanvas = (new Canvas(maskBitmap));
maskRenderCanvas.clipRect(
    mMaskOffsetX,
    mMaskOffsetY,
    mMaskOffsetX + maskBitmap.getWidth(),
    mMaskOffsetY + maskBitmap.getHeight());
maskRenderCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
super.dispatchDraw(maskRenderCanvas);

maskRenderCanvas.drawBitmap(maskBitmap, mMaskOffsetX, mMaskOffsetY, mMaskPaint);
canvas.drawBitmap(maskBitmap, 0, 0, null);

次に、変位を認識し、ValueAnimator を通じてリアルタイム描画フラッシュ効果をトリガーします。

mMaskTranslation.set(-width, 0, width, 0);
mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f + (float) mRepeatDelay / mDuration);
mAnimator.setDuration(mDuration + mRepeatDelay);
mAnimator.setRepeatCount(mRepeatCount);
mAnimator.setRepeatMode(mRepeatMode);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
    float value = Math.max(0.0f, Math.min(1.0f, (Float) animation.getAnimatedValue()));
    mMaskOffsetX = (int) (mMaskTranslation.fromX * (1 - value) + mMaskTranslation.toX * value);
    mMaskOffsetY = (int) (mMaskTranslation.fromY * (1 - value) + mMaskTranslation.toY * value);
    invalidate();
}
});

最終的な効果を次の図に示します。

写真

△デイモード

写真

△ナイトモード

写真

△ダークモード

04 エピローグ

上記のコンテンツでは、マスクをベースにしたスイープ効果を紹介しましたが、マスクの一般的な用途としては、角丸効果、突き刺すポートレート弾幕、初心者ガイドの穴掘り効果の描画、または宝くじスクラッチ効果です。

レンダリング技術は主にiOSシステムのCoreAnimationフレームワークやAndroidのViewシステムに適用されます。

Core Animation は通常、アニメーションを効率的かつ便利に実装するために iOS で使用されます。グラフィックのレンダリングとアニメーション操作に CALayer を使用します。Apple はマスキング サポートを UIView で直接提供しませんが、基礎となる CALayer で実装します。これにより、開発者はマスクを柔軟に制御および変更して、より強力な効果を実現できます。そして Android は、Canvas を通じて実現できる、より柔軟で強力なエフェクトを作りたいと考えています。

- 終わり -

推奨読書:

Android SDKのセキュリティ強化の問題と分析

検索セマンティックモデルの大規模定量的実践

効率的な分散ログ サービス プラットフォームを設計する方法

ビデオおよび画像検索におけるマルチモーダルセマンティックマッチングモデル: 原理、含意、応用および展望

Baidu オフライン リソース管理

Baidu APP iOS 端末パッケージサイズ 50M 最適化実践 (3) リソース最適化

おすすめ

転載: blog.csdn.net/lihui49/article/details/131529241
おすすめ