1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.settings;
17 
18 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
19 
20 import android.animation.Animator;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.ObjectAnimator;
23 import android.animation.ValueAnimator;
24 import android.graphics.Canvas;
25 import android.graphics.Color;
26 import android.graphics.Paint;
27 import android.graphics.RectF;
28 import android.util.Property;
29 import android.view.View;
30 
31 import androidx.preference.Preference;
32 import androidx.recyclerview.widget.RecyclerView;
33 import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
34 import androidx.recyclerview.widget.RecyclerView.State;
35 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
36 
37 import com.android.launcher3.util.Themes;
38 
39 /**
40  * Utility class for highlighting a preference
41  */
42 public class PreferenceHighlighter extends ItemDecoration implements Runnable {
43 
44     private static final Property<PreferenceHighlighter, Integer> HIGHLIGHT_COLOR =
45             new Property<PreferenceHighlighter, Integer>(Integer.TYPE, "highlightColor") {
46 
47                 @Override
48                 public Integer get(PreferenceHighlighter highlighter) {
49                     return highlighter.mHighlightColor;
50                 }
51 
52                 @Override
53                 public void set(PreferenceHighlighter highlighter, Integer value) {
54                     highlighter.mHighlightColor = value;
55                     highlighter.mRv.invalidateItemDecorations();
56                 }
57             };
58 
59     private static final long HIGHLIGHT_DURATION = 15000L;
60     private static final long HIGHLIGHT_FADE_OUT_DURATION = 500L;
61     private static final long HIGHLIGHT_FADE_IN_DURATION = 200L;
62     private static final int END_COLOR = setColorAlphaBound(Color.WHITE, 0);
63 
64     private final Paint mPaint = new Paint();
65     private final RecyclerView mRv;
66     private final int mIndex;
67     private final Preference mPreference;
68     private final RectF mDrawRect = new RectF();
69 
70     private boolean mHighLightStarted = false;
71     private int mHighlightColor = END_COLOR;
72 
PreferenceHighlighter(RecyclerView rv, int index, Preference preference)73     public PreferenceHighlighter(RecyclerView rv, int index, Preference preference) {
74         mRv = rv;
75         mIndex = index;
76         mPreference = preference;
77     }
78 
79     @Override
run()80     public void run() {
81         mRv.addItemDecoration(this);
82         mRv.smoothScrollToPosition(mIndex);
83     }
84 
85     @Override
onDraw(Canvas c, RecyclerView parent, State state)86     public void onDraw(Canvas c, RecyclerView parent, State state) {
87         ViewHolder holder = parent.findViewHolderForAdapterPosition(mIndex);
88         if (holder == null) {
89             return;
90         }
91         if (!mHighLightStarted && state.getRemainingScrollVertical() != 0) {
92             // Wait until scrolling stopped
93             return;
94         }
95 
96         if (!mHighLightStarted) {
97             // Start highlight
98             int colorTo = setColorAlphaBound(Themes.getColorAccent(mRv.getContext()), 66);
99             ObjectAnimator anim = ObjectAnimator.ofArgb(this, HIGHLIGHT_COLOR, END_COLOR,
100                     colorTo);
101             anim.setDuration(HIGHLIGHT_FADE_IN_DURATION);
102             anim.setRepeatMode(ValueAnimator.REVERSE);
103             anim.setRepeatCount(4);
104             anim.addListener(new AnimatorListenerAdapter() {
105                 @Override
106                 public void onAnimationEnd(Animator animation) {
107                     removeHighlight();
108                 }
109             });
110             anim.start();
111             mHighLightStarted = true;
112         }
113 
114         View view = holder.itemView;
115         mPaint.setColor(mHighlightColor);
116         mDrawRect.set(0, view.getY(), parent.getWidth(), view.getY() + view.getHeight());
117         if (mPreference instanceof HighlightDelegate) {
118             ((HighlightDelegate) mPreference).offsetHighlight(view, mDrawRect);
119         }
120         c.drawRect(mDrawRect, mPaint);
121     }
122 
removeHighlight()123     private void removeHighlight() {
124         ObjectAnimator anim = ObjectAnimator.ofArgb(
125                 this, HIGHLIGHT_COLOR, mHighlightColor, END_COLOR);
126         anim.setDuration(HIGHLIGHT_FADE_OUT_DURATION);
127         anim.setStartDelay(HIGHLIGHT_DURATION);
128         anim.addListener(new AnimatorListenerAdapter() {
129             @Override
130             public void onAnimationEnd(Animator animation) {
131                 mRv.removeItemDecoration(PreferenceHighlighter.this);
132             }
133         });
134         anim.start();
135     }
136 
137     /**
138      * Interface to be implemented by a preference to customize the highlight are
139      */
140     public interface HighlightDelegate {
141 
142         /**
143          * Allows the preference to update the highlight area
144          */
offsetHighlight(View prefView, RectF bounds)145         void offsetHighlight(View prefView, RectF bounds);
146 
147     }
148 }
149