Commit 3b23d140 authored by Boris Sazonov's avatar Boris Sazonov Committed by Commit Bot

[Android] Implement StateListDrawableBuilder

This CL adds StateListDrawableBuilder, a helper class that simplifies
StateListDrawable and AnimatedListDrawable creation.

Bug: 814728
Change-Id: If2f07e1dab0cd7f5a6d46c81e37f4153b6259caa
Reviewed-on: https://chromium-review.googlesource.com/1101325
Commit-Queue: Boris Sazonov <bsazonov@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#568374}
parent 7a4ba5be
......@@ -228,6 +228,7 @@ android_library("ui_full_java") {
"java/src/org/chromium/ui/display/DisplayUtil.java",
"java/src/org/chromium/ui/display/PhysicalDisplayAndroid.java",
"java/src/org/chromium/ui/display/VirtualDisplayAndroid.java",
"java/src/org/chromium/ui/drawable/StateListDrawableBuilder.java",
"java/src/org/chromium/ui/events/devices/InputDeviceObserver.java",
"java/src/org/chromium/ui/gfx/BitmapHelper.java",
"java/src/org/chromium/ui/gfx/ViewConfigurationHelper.java",
......@@ -295,7 +296,11 @@ junit_binary("ui_junit_tests") {
java_files = [
"junit/src/org/chromium/ui/base/ClipboardTest.java",
"junit/src/org/chromium/ui/base/SelectFileDialogTest.java",
"junit/src/org/chromium/ui/drawable/StateListDrawableBuilderTest.java",
"junit/src/org/chromium/ui/text/SpanApplierTest.java",
"junit/src/org/chromium/ui/shadows/ShadowAppCompatResources.java",
"junit/src/org/chromium/ui/shadows/ShadowAppCompatResourcesTest.java",
"junit/src/org/chromium/ui/shadows/ShadowAnimatedStateListDrawable.java",
"junit/src/org/chromium/ui/widget/AnchoredPopupWindowTest.java",
]
deps = [
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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.annotation.TargetApi;
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 android.os.Build;
import android.support.annotation.DrawableRes;
import android.support.v7.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() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return buildPostL();
} else {
return buildPreL();
}
}
private StateListDrawable buildPreL() {
// Create StateListDrawable on API levels where AnimatedStateListDrawable is not available.
StateListDrawable result = new StateListDrawable();
int size = mStates.size();
for (int i = 0; i < size; ++i) {
State state = mStates.get(i);
Drawable drawable = AppCompatResources.getDrawable(mContext, state.getDrawable());
assert drawable != null;
result.addState(state.getStateSet(), drawable);
}
return result;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private StateListDrawable buildPostL() {
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(),
castToAnimatableDrawable(drawable), false);
}
return result;
}
@SuppressWarnings("unchecked") // Need Java 8 to cast to intersection types
private static <T> T castToAnimatableDrawable(Drawable drawable) {
if (!(drawable instanceof Animatable)) throw new IllegalArgumentException("drawable");
return (T) drawable;
}
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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 static org.junit.Assert.assertEquals;
import static org.robolectric.Shadows.shadowOf;
import android.graphics.drawable.AnimatedStateListDrawable;
import android.graphics.drawable.StateListDrawable;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowStateListDrawable;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.ui.shadows.ShadowAnimatedStateListDrawable;
import org.chromium.ui.shadows.ShadowAppCompatResources;
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE,
shadows = {ShadowAppCompatResources.class, ShadowAnimatedStateListDrawable.class})
public class StateListDrawableBuilderTest {
private static final int[] CHECKED_STATE = new int[] {android.R.attr.state_checked};
private static final int[] WILDCARD_STATE = new int[0];
private static final int CHECKED_DRAWABLE = 34567;
private static final int DEFAULT_DRAWABLE = 45678;
@Test
@Config(sdk = 18)
public void testPreL() {
StateListDrawableBuilder b = new StateListDrawableBuilder(RuntimeEnvironment.application);
b.addState(CHECKED_DRAWABLE, android.R.attr.state_checked);
b.addState(DEFAULT_DRAWABLE, WILDCARD_STATE);
StateListDrawable result = b.build();
assertEquals(result.getClass(), StateListDrawable.class);
ShadowStateListDrawable drawable = shadowOf(result);
assertEquals(CHECKED_DRAWABLE,
shadowOf(drawable.getDrawableForState(CHECKED_STATE)).getCreatedFromResId());
assertEquals(DEFAULT_DRAWABLE,
shadowOf(drawable.getDrawableForState(WILDCARD_STATE)).getCreatedFromResId());
}
@Test
@Config(sdk = 21)
public void testPostL() {
StateListDrawableBuilder b = new StateListDrawableBuilder(RuntimeEnvironment.application);
b.addState(CHECKED_DRAWABLE, android.R.attr.state_checked);
b.addState(DEFAULT_DRAWABLE, WILDCARD_STATE);
StateListDrawable result = b.build();
assertEquals(result.getClass(), AnimatedStateListDrawable.class);
ShadowAnimatedStateListDrawable drawable = Shadow.extract(result);
assertEquals(CHECKED_DRAWABLE,
shadowOf(drawable.getDrawableForState(CHECKED_STATE)).getCreatedFromResId());
assertEquals(DEFAULT_DRAWABLE,
shadowOf(drawable.getDrawableForState(WILDCARD_STATE)).getCreatedFromResId());
}
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.ui.shadows;
import android.graphics.drawable.AnimatedStateListDrawable;
import android.graphics.drawable.Drawable;
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowStateListDrawable;
@Implements(AnimatedStateListDrawable.class)
public class ShadowAnimatedStateListDrawable extends ShadowStateListDrawable {
public void addState(int[] stateSet, Drawable drawable, int stateId) {
addState(stateSet, drawable);
}
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.ui.shadows;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.content.res.AppCompatResources;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowDrawable;
@Implements(AppCompatResources.class)
public class ShadowAppCompatResources {
@Implementation
@Nullable
public static Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) {
return ShadowDrawable.createFromResourceId(resId);
}
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.ui.shadows;
import static org.junit.Assert.assertEquals;
import static org.robolectric.Shadows.shadowOf;
import android.graphics.drawable.Drawable;
import android.support.v7.content.res.AppCompatResources;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.chromium.base.test.BaseRobolectricTestRunner;
/** Tests logic in the ShadowAppCompatResources class. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE, shadows = {ShadowAppCompatResources.class})
public class ShadowAppCompatResourcesTest {
private static final int DRAWABLE_RES_ID = 34567;
@Test
public void testShadowAppCompatResources() {
Drawable drawable =
AppCompatResources.getDrawable(RuntimeEnvironment.application, DRAWABLE_RES_ID);
assertEquals(DRAWABLE_RES_ID, shadowOf(drawable).getCreatedFromResId());
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment