Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 247641c4 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Search highlight polish"

parents 809db7a3 730ea972
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -16,5 +16,5 @@
  -->

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:alpha="0.1" android:color="?android:attr/colorAccent" />
    <item android:alpha="0.26" android:color="?android:attr/colorAccent" />
</selector>
 No newline at end of file
+82 −13
Original line number Diff line number Diff line
@@ -18,7 +18,12 @@ package com.android.settings.widget;

import static com.android.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.PreferenceGroup;
@@ -27,6 +32,7 @@ import android.support.v7.preference.PreferenceScreen;
import android.support.v7.preference.PreferenceViewHolder;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;

@@ -35,14 +41,20 @@ import com.android.settings.SettingsPreferenceFragment;

public class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter {

    private static final String TAG = "HighlightableAdapter";
    @VisibleForTesting
    static final long DELAY_HIGHLIGHT_DURATION_MILLIS = 600L;
    private static final long HIGHLIGHT_DURATION = 5000L;
    private static final long HIGHLIGHT_DURATION = 15000L;
    private static final long HIGHLIGHT_FADE_OUT_DURATION = 500L;
    private static final long HIGHLIGHT_FADE_IN_DURATION = 200L;

    @VisibleForTesting
    final int mHighlightColor;
    @VisibleForTesting
    boolean mFadeInAnimated;

    private final int mHighlightColor;
    private final int mNormalBackgroundRes;
    private final String mHighlightKey;

    private boolean mHighlightRequested;
    private int mHighlightPosition = RecyclerView.NO_POSITION;

@@ -102,14 +114,11 @@ public class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter
    void updateBackground(PreferenceViewHolder holder, int position) {
        View v = holder.itemView;
        if (position == mHighlightPosition) {
            v.setBackgroundColor(mHighlightColor);
            v.setTag(R.id.preference_highlighted, true);
            v.postDelayed(() -> {
                mHighlightPosition = RecyclerView.NO_POSITION;
                removeHighlightBackground(v);
            }, HIGHLIGHT_DURATION);
            // This position should be highlighted. If it's highlighted before - skip animation.
            addHighlightBackground(v, !mFadeInAnimated);
        } else if (Boolean.TRUE.equals(v.getTag(R.id.preference_highlighted))) {
            removeHighlightBackground(v);
            // View with highlight is reused for a view that should not have highlight
            removeHighlightBackground(v, false /* animate */);
        }
    }

@@ -123,7 +132,7 @@ public class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter
                return;
            }
            mHighlightRequested = true;
            recyclerView.getLayoutManager().scrollToPosition(position);
            recyclerView.smoothScrollToPosition(position);
            mHighlightPosition = position;
            notifyItemChanged(position);
        }, DELAY_HIGHLIGHT_DURATION_MILLIS);
@@ -133,8 +142,68 @@ public class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter
        return mHighlightRequested;
    }

    private void removeHighlightBackground(View v) {
    @VisibleForTesting
    void requestRemoveHighlightDelayed(View v) {
        v.postDelayed(() -> {
            mHighlightPosition = RecyclerView.NO_POSITION;
            removeHighlightBackground(v, true /* animate */);
        }, HIGHLIGHT_DURATION);
    }

    private void addHighlightBackground(View v, boolean animate) {
        v.setTag(R.id.preference_highlighted, true);
        if (!animate) {
            v.setBackgroundColor(mHighlightColor);
            Log.d(TAG, "AddHighlight: Not animation requested - setting highlight background");
            requestRemoveHighlightDelayed(v);
            return;
        }
        mFadeInAnimated = true;
        final int colorFrom = Color.WHITE;
        final int colorTo = mHighlightColor;
        final ValueAnimator fadeInLoop = ValueAnimator.ofObject(
                new ArgbEvaluator(), colorFrom, colorTo);
        fadeInLoop.setDuration(HIGHLIGHT_FADE_IN_DURATION);
        fadeInLoop.addUpdateListener(
                animator -> v.setBackgroundColor((int) animator.getAnimatedValue()));
        fadeInLoop.setRepeatMode(ValueAnimator.REVERSE);
        fadeInLoop.setRepeatCount(4);
        fadeInLoop.start();
        Log.d(TAG, "AddHighlight: starting fade in animation");
        requestRemoveHighlightDelayed(v);
    }

    private void removeHighlightBackground(View v, boolean animate) {
        if (!animate) {
            v.setTag(R.id.preference_highlighted, false);
            v.setBackgroundResource(mNormalBackgroundRes);
            Log.d(TAG, "RemoveHighlight: No animation requested - setting normal background");
            return;
        }

        if (!Boolean.TRUE.equals(v.getTag(R.id.preference_highlighted))) {
            // Not highlighted, no-op
            Log.d(TAG, "RemoveHighlight: Not highlighted - skipping");
            return;
        }
        int colorFrom = mHighlightColor;
        int colorTo = Color.WHITE;

        v.setTag(R.id.preference_highlighted, false);
        final ValueAnimator colorAnimation = ValueAnimator.ofObject(
                new ArgbEvaluator(), colorFrom, colorTo);
        colorAnimation.setDuration(HIGHLIGHT_FADE_OUT_DURATION);
        colorAnimation.addUpdateListener(
                animator -> v.setBackgroundColor((int) animator.getAnimatedValue()));
        colorAnimation.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                // Animation complete - the background is now white. Change to mNormalBackgroundRes
                // so it is white and has ripple on touch.
                v.setBackgroundResource(mNormalBackgroundRes);
            }
        });
        colorAnimation.start();
        Log.d(TAG, "Starting fade out animation");
    }
}
+29 −3
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@@ -71,8 +73,8 @@ public class HighlightablePreferenceGroupAdapterTest {
        MockitoAnnotations.initMocks(this);
        mContext = RuntimeEnvironment.application;
        when(mPreferenceCatetory.getContext()).thenReturn(mContext);
        mAdapter = new HighlightablePreferenceGroupAdapter(mPreferenceCatetory, TEST_KEY,
                false /* highlighted*/);
        mAdapter = spy(new HighlightablePreferenceGroupAdapter(mPreferenceCatetory, TEST_KEY,
                false /* highlighted*/));
        mViewHolder = PreferenceViewHolder.createInstanceForTests(
                View.inflate(mContext, R.layout.app_preference_item, null));
    }
@@ -163,12 +165,36 @@ public class HighlightablePreferenceGroupAdapterTest {
    }

    @Test
    public void updateBackground_highlight_shouldChangeBackgroundAndSetHighlightedTag() {
    public void updateBackground_highlight_shouldAnimateBackgroundAndSetHighlightedTag() {
        ReflectionHelpers.setField(mAdapter, "mHighlightPosition", 10);
        assertThat(mAdapter.mFadeInAnimated).isFalse();

        mAdapter.updateBackground(mViewHolder, 10);

        assertThat(mAdapter.mFadeInAnimated).isTrue();
        assertThat(mViewHolder.itemView.getBackground()).isInstanceOf(ColorDrawable.class);
        assertThat(mViewHolder.itemView.getTag(R.id.preference_highlighted)).isEqualTo(true);
        verify(mAdapter).requestRemoveHighlightDelayed(mViewHolder.itemView);
    }

    @Test
    public void updateBackgroundTwice_highlight_shouldAnimateOnce() {
        ReflectionHelpers.setField(mAdapter, "mHighlightPosition", 10);
        ReflectionHelpers.setField(mViewHolder, "itemView", spy(mViewHolder.itemView));
        assertThat(mAdapter.mFadeInAnimated).isFalse();
        mAdapter.updateBackground(mViewHolder, 10);
        // mFadeInAnimated change from false to true - indicating background change is scheduled
        // through animation.
        assertThat(mAdapter.mFadeInAnimated).isTrue();
        // remove highlight should be requested.
        verify(mAdapter).requestRemoveHighlightDelayed(mViewHolder.itemView);

        ReflectionHelpers.setField(mAdapter, "mHighlightPosition", 10);
        mAdapter.updateBackground(mViewHolder, 10);
        // only sets background color once - if it's animation this would be called many times
        verify(mViewHolder.itemView).setBackgroundColor(mAdapter.mHighlightColor);
        // remove highlight should be requested.
        verify(mAdapter, times(2)).requestRemoveHighlightDelayed(mViewHolder.itemView);
    }

    @Test