LineageParts: Fix animation for search results
* Current implementation of the highlight when clicking a search result
pointing to a LineageParts setting isn't working at all
* Kang the widget from settings and adapt it accordingly
Test:
Before:
Search for any AOSP setting (not title)
Click it -> it highlights (by flashing)
Search for any setting from LineageOS, e.g. "arrow keys"
Click it -> won't highlight
After:
Both results behave the same
Change-Id: Ib7e5edc3c62084a8217aa3270cd71cc57389f8a1
diff --git a/res/color/preference_highlight_color.xml b/res/color/preference_highlight_color.xml
new file mode 100644
index 0000000..f8d54d4
--- /dev/null
+++ b/res/color/preference_highlight_color.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:alpha="0.26" android:color="?android:attr/colorAccent" />
+</selector>
\ No newline at end of file
diff --git a/res/values/ids.xml b/res/values/ids.xml
new file mode 100644
index 0000000..0b526c5
--- /dev/null
+++ b/res/values/ids.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The LineageOS 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.
+-->
+
+<resources>
+ <item type="id" name="preference_highlighted" />
+</resources>
diff --git a/src/com/shiftos/shiftparts/SettingsPreferenceFragment.java b/src/com/shiftos/shiftparts/SettingsPreferenceFragment.java
index 68c2c13..5874556 100644
--- a/src/com/shiftos/shiftparts/SettingsPreferenceFragment.java
+++ b/src/com/shiftos/shiftparts/SettingsPreferenceFragment.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2020 The LineageOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -49,6 +50,7 @@
import com.shiftos.shiftparts.widget.CustomDialogPreference;
import com.shiftos.shiftparts.widget.DialogCreatable;
import com.shiftos.shiftparts.widget.FloatingActionButton;
+import com.shiftos.shiftparts.widget.HighlightablePreferenceGroupAdapter;
import com.shiftos.shiftparts.widget.LayoutPreference;
import java.util.Arrays;
@@ -263,8 +265,11 @@
}
public void highlightPreferenceIfNeeded() {
- if (isAdded() && !mPreferenceHighlighted &&!TextUtils.isEmpty(mPreferenceKey)) {
- highlightPreference(mPreferenceKey);
+ if (!isAdded()) {
+ return;
+ }
+ if (mAdapter != null) {
+ mAdapter.requestHighlight(getView(), getListView());
}
}
@@ -362,24 +367,6 @@
return mEmptyView;
}
- /**
- * Return a valid ListView position or -1 if none is found
- */
- private int canUseListViewForHighLighting(String key) {
- if (getListView() == null) {
- return -1;
- }
-
- RecyclerView listView = getListView();
- RecyclerView.Adapter adapter = listView.getAdapter();
-
- if (adapter != null && adapter instanceof PreferenceGroupAdapter) {
- return findListPositionFromKey((PreferenceGroupAdapter) adapter, key);
- }
-
- return -1;
- }
-
@Override
public RecyclerView.LayoutManager onCreateLayoutManager() {
mLayoutManager = new LinearLayoutManager(getContext());
@@ -388,7 +375,12 @@
@Override
protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
- mAdapter = new HighlightablePreferenceGroupAdapter(preferenceScreen);
+ final Bundle arguments = getArguments();
+ mAdapter = new HighlightablePreferenceGroupAdapter(preferenceScreen,
+ arguments == null
+ ? null
+ : arguments.getString(PartsActivity.EXTRA_FRAGMENT_ARG_KEY),
+ mPreferenceHighlighted);
return mAdapter;
}
@@ -422,21 +414,6 @@
return mPreferenceCache.size();
}
- private void highlightPreference(String key) {
- final int position = canUseListViewForHighLighting(key);
- if (position >= 0) {
- mPreferenceHighlighted = true;
- mLayoutManager.scrollToPosition(position);
-
- getView().postDelayed(new Runnable() {
- @Override
- public void run() {
- mAdapter.highlight(position);
- }
- }, DELAY_HIGHLIGHT_DURATION_MILLIS);
- }
- }
-
private int findListPositionFromKey(PreferenceGroupAdapter adapter, String key) {
final int count = adapter.getItemCount();
for (int n = 0; n < count; n++) {
@@ -776,34 +753,4 @@
return false;
}
}
-
- public static class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter {
-
- private int mHighlightPosition = -1;
-
- public HighlightablePreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
- super(preferenceGroup);
- }
-
- public void highlight(int position) {
- mHighlightPosition = position;
- notifyDataSetChanged();
- }
-
- @Override
- public void onBindViewHolder(PreferenceViewHolder holder, int position) {
- super.onBindViewHolder(holder, position);
- if (position == mHighlightPosition) {
- View v = holder.itemView;
- if (v.getBackground() != null) {
- final int centerX = v.getWidth() / 2;
- final int centerY = v.getHeight() / 2;
- v.getBackground().setHotspot(centerX, centerY);
- }
- v.setPressed(true);
- v.setPressed(false);
- mHighlightPosition = -1;
- }
- }
- }
}
diff --git a/src/com/shiftos/shiftparts/widget/HighlightablePreferenceGroupAdapter.java b/src/com/shiftos/shiftparts/widget/HighlightablePreferenceGroupAdapter.java
new file mode 100644
index 0000000..1f229f3
--- /dev/null
+++ b/src/com/shiftos/shiftparts/widget/HighlightablePreferenceGroupAdapter.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2020 The LineageOS 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 com.shiftos.shiftparts.widget;
+
+import static com.shiftos.shiftparts.PartsActivity.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.text.TextUtils;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.View;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceGroupAdapter;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.PreferenceViewHolder;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.shiftos.shiftparts.R;
+import com.shiftos.shiftparts.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 = 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 mNormalBackgroundRes;
+ private final String mHighlightKey;
+ private boolean mHighlightRequested;
+ private int mHighlightPosition = RecyclerView.NO_POSITION;
+
+ public HighlightablePreferenceGroupAdapter(PreferenceGroup preferenceGroup, String key,
+ boolean highlightRequested) {
+ super(preferenceGroup);
+ mHighlightKey = key;
+ mHighlightRequested = highlightRequested;
+ final Context context = preferenceGroup.getContext();
+ final TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground,
+ outValue, true /* resolveRefs */);
+ mNormalBackgroundRes = outValue.resourceId;
+ mHighlightColor = context.getColor(R.color.preference_highlight_color);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder, int position) {
+ super.onBindViewHolder(holder, position);
+ updateBackground(holder, position);
+ }
+
+ @VisibleForTesting
+ void updateBackground(PreferenceViewHolder holder, int position) {
+ View v = holder.itemView;
+ if (position == mHighlightPosition) {
+ // 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))) {
+ // View with highlight is reused for a view that should not have highlight
+ removeHighlightBackground(v, false /* animate */);
+ }
+ }
+
+ public void requestHighlight(View root, RecyclerView recyclerView) {
+ if (mHighlightRequested || recyclerView == null || TextUtils.isEmpty(mHighlightKey)) {
+ return;
+ }
+ root.postDelayed(() -> {
+ final int position = getPreferenceAdapterPosition(mHighlightKey);
+ if (position < 0) {
+ return;
+ }
+ mHighlightRequested = true;
+ recyclerView.smoothScrollToPosition(position);
+ mHighlightPosition = position;
+ notifyItemChanged(position);
+ }, DELAY_HIGHLIGHT_DURATION_MILLIS);
+ }
+
+ public boolean isHighlightRequested() {
+ return mHighlightRequested;
+ }
+
+ @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");
+ }
+}