chromium/ui/android/java/src/org/chromium/ui/drawable/StateListDrawableBuilder.java

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.ui.drawable;

import android.content.Context;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.AnimatedStateListDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;

import androidx.annotation.DrawableRes;
import androidx.appcompat.content.res.AppCompatResources;

import java.util.ArrayList;
import java.util.List;

/**
 * Helper that simplifies StateListDrawable and AnimatedStateListDrawable creation. Stateful
 * drawables have to be created in Java for now, as drawables specified in XML can't reference
 * vector drawables on platform versions where VectorDrawableCompat is used (API level 23 and
 * below).
 *
 * {@link #build()} will instantiate AnimatedStateListDrawable on platforms where it is supported
 * (API level 21+). On older APIs, transition animations will be ignored and StateListDrawable will
 * be instantiated instead.
 *
 * Usage:
 * StateListDrawableBuilder builder = new StateListDrawableBuilder(context);
 * StateListDrawableBuilder.State checked =
 *         builder.addState(R.drawable.checked, android.R.attr.state_checked);
 * StateListDrawableBuilder.State unchecked = builder.addState(R.drawable.unchecked);
 * builder.addTransition(checked, unchecked, R.drawable.transition_checked_unchecked);
 * builder.addTransition(unchecked, checked, R.drawable.transition_unchecked_checked);
 * StateListDrawable drawable = builder.build();
 */
public class StateListDrawableBuilder {
    /** Identifies single state of the drawable. Used by {@link #addTransition}. */
    public static class State {
        private final @DrawableRes int mDrawable;
        private final int[] mStateSet;
        private final int mStateId;

        private State(@DrawableRes int drawable, int[] stateSet, int stateId) {
            mDrawable = drawable;
            mStateSet = stateSet;
            mStateId = stateId;
        }

        private @DrawableRes int getDrawable() {
            return mDrawable;
        }

        private int[] getStateSet() {
            return mStateSet;
        }

        private int getStateId() {
            return mStateId;
        }
    }

    private static class Transition {
        private final @DrawableRes int mDrawable;
        private final int mFromStateId;
        private final int mToStateId;

        private Transition(@DrawableRes int drawable, int fromStateId, int toStateId) {
            mDrawable = drawable;
            mFromStateId = fromStateId;
            mToStateId = toStateId;
        }

        private @DrawableRes int getDrawable() {
            return mDrawable;
        }

        private int getFromId() {
            return mFromStateId;
        }

        private int getToId() {
            return mToStateId;
        }
    }

    private final Context mContext;
    private final List<State> mStates = new ArrayList<>();
    private final List<Transition> mTransitions = new ArrayList<>();

    public StateListDrawableBuilder(Context context) {
        mContext = context;
    }

    /**
     * Add state to the drawable. Please note that order of calls to this method is important, as
     * StateListDrawable will pick the first state which stateSet matches View state.
     * @param drawable Id of the drawable for the added state. May refer to a vector drawable.
     * @param stateSet Array of state ids that specify the state. See {@link android.R.attr}
     *         for the list of state ids provided by the platform.
     */
    public State addState(@DrawableRes int drawable, int... stateSet) {
        int nextStateId = mStates.size() + 1; // State ids should be greater than 1.
        State state = new State(drawable, stateSet, nextStateId);
        mStates.add(state);
        return state;
    }

    /**
     * Add transition animation to the stateful drawable.
     * @param from The state of the stateful drawable before the transition.
     * @param to The state of the stateful drawable after the transition.
     * @param drawable Id of the animated drawable for the transition. Must refer to animated vector
     *         drawable.
     */
    public void addTransition(State from, State to, @DrawableRes int drawable) {
        assert mStates.contains(from) && mStates.contains(to) : "State from a different builder!";
        Transition transition = new Transition(drawable, from.getStateId(), to.getStateId());
        mTransitions.add(transition);
    }

    /**
     * Build drawable from added states and transitions.
     * @return AnimatedStateListDrawable if platform supports it, StateListDrawable otherwise.
     */
    public StateListDrawable build() {
        AnimatedStateListDrawable result = new AnimatedStateListDrawable();
        int statesSize = mStates.size();
        for (int i = 0; i < statesSize; ++i) {
            State state = mStates.get(i);
            Drawable drawable = AppCompatResources.getDrawable(mContext, state.getDrawable());
            assert drawable != null;
            result.addState(state.getStateSet(), drawable, state.getStateId());
        }
        int transitionsSize = mTransitions.size();
        for (int i = 0; i < transitionsSize; ++i) {
            Transition transition = mTransitions.get(i);
            Drawable drawable = AppCompatResources.getDrawable(mContext, transition.getDrawable());
            result.addTransition(
                    transition.getFromId(),
                    transition.getToId(),
                    (Drawable & Animatable) (drawable),
                    false);
        }
        return result;
    }
}