public class

MotionEffect

extends MotionHelper

 java.lang.Object

↳View

androidx.constraintlayout.widget.ConstraintHelper

androidx.constraintlayout.motion.widget.MotionHelper

↳androidx.constraintlayout.helper.widget.MotionEffect

Gradle dependencies

compile group: 'androidx.constraintlayout', name: 'constraintlayout', version: '2.2.0-beta01'

  • groupId: androidx.constraintlayout
  • artifactId: constraintlayout
  • version: 2.2.0-beta01

Artifact androidx.constraintlayout:constraintlayout:2.2.0-beta01 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.constraintlayout:constraintlayout com.android.support.constraint:constraint-layout

Overview

MotionHelper that automatically inserts keyframes for views moving in a given direction, out of:

  • NORTH
  • SOUTH
  • EAST
  • WEST
By default, will pick the opposite of the dominant direction (e.g. elements /not/ moving in the dominant direction will have the keyframes inserted).

Summary

Fields
public static final intAUTO

public static final intEAST

public static final intNORTH

public static final intSOUTH

public static final java.lang.StringTAG

public static final intWEST

from MotionHelperviews[]
from ConstraintHelperCHILD_TAG, mCount, mHelperWidget, mIds[], mMap, mReferenceIds, mReferenceTags, mUseViewMeasure, myContext
Constructors
publicMotionEffect(Context context)

publicMotionEffect(Context context, AttributeSet attrs)

publicMotionEffect(Context context, AttributeSet attrs, int defStyleAttr)

Methods
public booleanisDecorator()

public voidonPreSetup(MotionLayout motionLayout, java.util.HashMap<View, MotionController> controllerMap)

from MotionHelpergetProgress, init, isUsedOnShow, isUseOnHide, onFinishedMotionScene, onPostDraw, onPreDraw, onTransitionChange, onTransitionCompleted, onTransitionStarted, onTransitionTrigger, setProgress, setProgress
from ConstraintHelperaddView, applyHelperParams, applyLayoutFeatures, applyLayoutFeatures, applyLayoutFeaturesInConstraintSet, containsId, getReferencedIds, getViews, indexFromId, isChildOfHelper, loadParameters, onAttachedToWindow, onDraw, onMeasure, removeView, resolveRtl, setIds, setReferencedIds, setReferenceTags, setTag, updatePostConstraints, updatePostLayout, updatePostMeasure, updatePreDraw, updatePreLayout, updatePreLayout, validateParams
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final java.lang.String TAG

public static final int AUTO

public static final int NORTH

public static final int SOUTH

public static final int EAST

public static final int WEST

Constructors

public MotionEffect(Context context)

public MotionEffect(Context context, AttributeSet attrs)

public MotionEffect(Context context, AttributeSet attrs, int defStyleAttr)

Methods

public boolean isDecorator()

public void onPreSetup(MotionLayout motionLayout, java.util.HashMap<View, MotionController> controllerMap)

Source

/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.constraintlayout.helper.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import androidx.constraintlayout.motion.widget.Debug;
import androidx.constraintlayout.motion.widget.Key;
import androidx.constraintlayout.motion.widget.KeyAttributes;
import androidx.constraintlayout.motion.widget.KeyPosition;
import androidx.constraintlayout.motion.widget.MotionController;
import androidx.constraintlayout.motion.widget.MotionHelper;
import androidx.constraintlayout.motion.widget.MotionLayout;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.R;

import java.util.HashMap;

/**
 * MotionHelper that automatically inserts keyframes for views moving in a given
 * direction, out of:
 * <ul>
 *     <li>NORTH</li>
 *     <li>SOUTH</li>
 *     <li>EAST</li>
 *     <li>WEST</li>
 * </ul>
 *
 * By default, will pick the opposite of the dominant direction (e.g. elements /not/ moving
 * in the dominant direction will have the keyframes inserted).
 */
public class MotionEffect extends MotionHelper {
    public static final String TAG = "FadeMove";

    public static final int AUTO = -1;
    public static final int NORTH = 0;
    public static final int SOUTH = 1;
    public static final int EAST = 2;
    public static final int WEST = 3;

    private float mMotionEffectAlpha = 0.1f;
    private int mMotionEffectStart = 49;
    private int mMotionEffectEnd = 50;
    private int mMotionEffectTranslationX = 0;
    private int mMotionEffectTranslationY = 0;
    private boolean mMotionEffectStrictMove = true;
    private static final int UNSET = -1;
    private int mViewTransitionId = UNSET;

    private int mFadeMove = AUTO;

    public MotionEffect(Context context) {
        super(context);
    }

    public MotionEffect(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public MotionEffect(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        if (attrs != null) {
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MotionEffect);
            final int n = a.getIndexCount();
            for (int i = 0; i < n; i++) {
                int attr = a.getIndex(i);
                if (attr == R.styleable.MotionEffect_motionEffect_start) {
                    mMotionEffectStart = a.getInt(attr, mMotionEffectStart);
                    mMotionEffectStart = Math.max(Math.min(mMotionEffectStart, 99), 0);
                } else if (attr == R.styleable.MotionEffect_motionEffect_end) {
                    mMotionEffectEnd = a.getInt(attr, mMotionEffectEnd);
                    mMotionEffectEnd = Math.max(Math.min(mMotionEffectEnd, 99), 0);
                } else if (attr == R.styleable.MotionEffect_motionEffect_translationX) {
                    mMotionEffectTranslationX =
                            a.getDimensionPixelOffset(attr, mMotionEffectTranslationX);
                } else if (attr == R.styleable.MotionEffect_motionEffect_translationY) {
                    mMotionEffectTranslationY =
                            a.getDimensionPixelOffset(attr, mMotionEffectTranslationY);
                } else if (attr == R.styleable.MotionEffect_motionEffect_alpha) {
                    mMotionEffectAlpha = a.getFloat(attr, mMotionEffectAlpha);
                } else if (attr == R.styleable.MotionEffect_motionEffect_move) {
                    mFadeMove = a.getInt(attr, mFadeMove);
                } else if (attr == R.styleable.MotionEffect_motionEffect_strict) {
                    mMotionEffectStrictMove = a.getBoolean(attr, mMotionEffectStrictMove);
                } else if (attr == R.styleable.MotionEffect_motionEffect_viewTransition) {
                    mViewTransitionId = a.getResourceId(attr, mViewTransitionId);
                }
            }
            if (mMotionEffectStart == mMotionEffectEnd) {
                if (mMotionEffectStart > 0) {
                    mMotionEffectStart--;
                } else {
                    mMotionEffectEnd++;
                }
            }
            a.recycle();
        }
    }

    @Override
    public boolean isDecorator() {
        return true;
    }

    @Override
    public void onPreSetup(MotionLayout motionLayout,
                           HashMap<View,
            MotionController> controllerMap) {
        View[] views = getViews((ConstraintLayout) this.getParent());

        if (views == null) {
            Log.v(TAG, Debug.getLoc() + " views = null");
            return;
        }

        // Prepare a set of keyframes to be inserted

        KeyAttributes alpha1 = new KeyAttributes();
        KeyAttributes alpha2 = new KeyAttributes();
        alpha1.setValue(Key.ALPHA, mMotionEffectAlpha);
        alpha2.setValue(Key.ALPHA, mMotionEffectAlpha);
        alpha1.setFramePosition(mMotionEffectStart);
        alpha2.setFramePosition(mMotionEffectEnd);
        KeyPosition stick1 = new KeyPosition();
        stick1.setFramePosition(mMotionEffectStart);
        stick1.setType(KeyPosition.TYPE_CARTESIAN);
        stick1.setValue(KeyPosition.PERCENT_X, 0);
        stick1.setValue(KeyPosition.PERCENT_Y, 0);
        KeyPosition stick2 = new KeyPosition();
        stick2.setFramePosition(mMotionEffectEnd);
        stick2.setType(KeyPosition.TYPE_CARTESIAN);
        stick2.setValue(KeyPosition.PERCENT_X, 1);
        stick2.setValue(KeyPosition.PERCENT_Y, 1);

        KeyAttributes translationX1 = null;
        KeyAttributes translationX2 = null;
        if (mMotionEffectTranslationX > 0) {
            translationX1 = new KeyAttributes();
            translationX2 = new KeyAttributes();
            translationX1.setValue(Key.TRANSLATION_X, mMotionEffectTranslationX);
            translationX1.setFramePosition(mMotionEffectEnd);
            translationX2.setValue(Key.TRANSLATION_X, 0);
            translationX2.setFramePosition(mMotionEffectEnd - 1);
        }

        KeyAttributes translationY1 = null;
        KeyAttributes translationY2 = null;
        if (mMotionEffectTranslationY > 0) {
            translationY1 = new KeyAttributes();
            translationY2 = new KeyAttributes();
            translationY1.setValue(Key.TRANSLATION_Y, mMotionEffectTranslationY);
            translationY1.setFramePosition(mMotionEffectEnd);
            translationY2.setValue(Key.TRANSLATION_Y, 0);
            translationY2.setFramePosition(mMotionEffectEnd - 1);
        }

        int moveDirection = mFadeMove;
        if (mFadeMove == AUTO) {
            int[] direction = new int[4];
            // let's find out the general movement direction for all the referenced views
            for (int i = 0; i < views.length; i++) {
                MotionController mc = controllerMap.get(views[i]);
                if (mc == null) {
                    continue;
                }
                float x = mc.getFinalX() - mc.getStartX();
                float y = mc.getFinalY() - mc.getStartY();
                // look at the direction for this view, and increment the opposite direction
                // (as that's the one we will use to apply the fade)
                if (y < 0) {
                    direction[SOUTH]++;
                }
                if (y > 0) {
                    direction[NORTH]++;
                }
                if (x > 0) {
                    direction[WEST]++;
                }
                if (x < 0) {
                    direction[EAST]++;
                }
            }
            int max = direction[0];
            moveDirection = 0;
            for (int i = 1; i < 4; i++) {
                if (max < direction[i]) {
                    max = direction[i];
                    moveDirection = i;
                }
            }
        }

        for (int i = 0; i < views.length; i++) {
            MotionController mc = controllerMap.get(views[i]);
            if (mc == null) {
                continue;
            }
            float x = mc.getFinalX() - mc.getStartX();
            float y = mc.getFinalY() - mc.getStartY();
            boolean apply = true;

            // Any view that is moving in the given direction will have the fade applied
            // if move strict is true, also include views that are moving in diagonal, even
            // if they aren't moving in the opposite direction.
            if (moveDirection == NORTH) {
                if (y > 0 && (!mMotionEffectStrictMove || x == 0)) {
                    apply = false;
                }
            } else if (moveDirection == SOUTH) {
                if (y < 0 && (!mMotionEffectStrictMove || x == 0)) {
                    apply = false;
                }
            } else if (moveDirection == EAST) {
                if (x < 0 && (!mMotionEffectStrictMove || y == 0)) {
                    apply = false;
                }
            } else if (moveDirection == WEST) {
                if (x > 0 && (!mMotionEffectStrictMove || y == 0)) {
                    apply = false;
                }
            }

            if (apply) {
                if (mViewTransitionId == UNSET) {
                    mc.addKey(alpha1);
                    mc.addKey(alpha2);
                    mc.addKey(stick1);
                    mc.addKey(stick2);
                    if (mMotionEffectTranslationX > 0) {
                        mc.addKey(translationX1);
                        mc.addKey(translationX2);
                    }
                    if (mMotionEffectTranslationY > 0) {
                        mc.addKey(translationY1);
                        mc.addKey(translationY2);
                    }
                } else {
                    motionLayout.applyViewTransition(mViewTransitionId, mc);
                }
            }
        }
    }
}