316 lines
12 KiB
Java
316 lines
12 KiB
Java
package com.mogo.launcher.lancet;
|
|
|
|
import android.content.Context;
|
|
import android.content.res.ColorStateList;
|
|
import android.content.res.TypedArray;
|
|
import android.graphics.Color;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.graphics.drawable.StateListDrawable;
|
|
import android.os.Build;
|
|
import android.util.AttributeSet;
|
|
import android.util.Log;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.widget.ImageView;
|
|
import android.widget.TextView;
|
|
|
|
import androidx.annotation.Nullable;
|
|
import androidx.core.graphics.drawable.DrawableCompat;
|
|
import androidx.core.view.ViewCompat;
|
|
import com.knightboost.lancet.api.Origin;
|
|
import com.knightboost.lancet.api.Scope;
|
|
import com.knightboost.lancet.api.annotations.Group;
|
|
import com.knightboost.lancet.api.annotations.ImplementedInterface;
|
|
import com.knightboost.lancet.api.annotations.Insert;
|
|
import com.knightboost.lancet.api.annotations.ReplaceInvoke;
|
|
import com.knightboost.lancet.api.annotations.TargetClass;
|
|
import com.knightboost.lancet.api.annotations.TargetMethod;
|
|
import com.knightboost.lancet.api.annotations.Weaver;
|
|
import com.mogo.launcher.R;
|
|
|
|
@Weaver
|
|
@Group("view_pressed_state")
|
|
public class ViewPressedStateLancet {
|
|
|
|
public static class OnClickWrapper implements View.OnClickListener {
|
|
|
|
private final View.OnClickListener delegate;
|
|
|
|
public OnClickWrapper(View.OnClickListener delegate) {
|
|
this.delegate = delegate;
|
|
}
|
|
|
|
@Override
|
|
public void onClick(View v) {
|
|
Object tag = v.getTag(R.id.click_pressed_attr_id);
|
|
checkSetBgIfNeed(null, v, tag == null ? null : (AttributeSet) tag);
|
|
this.delegate.onClick(v);
|
|
}
|
|
}
|
|
|
|
|
|
public static class OnLongClickWrapper implements View.OnLongClickListener {
|
|
|
|
private final View.OnLongClickListener delegate;
|
|
|
|
public OnLongClickWrapper(View.OnLongClickListener delegate) {
|
|
this.delegate = delegate;
|
|
}
|
|
|
|
@Override
|
|
public boolean onLongClick(View v) {
|
|
Object tag = v.getTag(R.id.click_pressed_attr_id);
|
|
checkSetBgIfNeed(null, v, tag == null ? null : (AttributeSet) tag);
|
|
return delegate.onLongClick(v);
|
|
}
|
|
}
|
|
|
|
@TargetClass(value = "android.view.View", scope = Scope.ALL)
|
|
@TargetMethod(methodName = "setOnClickListener")
|
|
@ReplaceInvoke
|
|
public static void setOnClickListener(View view, View.OnClickListener listener) {
|
|
if (view == null) {
|
|
return;
|
|
}
|
|
if (listener == null) {
|
|
view.setOnClickListener(null);
|
|
return;
|
|
}
|
|
view.setOnClickListener(new OnClickWrapper(listener));
|
|
}
|
|
|
|
@TargetClass(value = "android.view.View", scope = Scope.ALL)
|
|
@TargetMethod(methodName = "setOnLongClickListener")
|
|
@ReplaceInvoke
|
|
public static void setOnLongClickListener(View view, View.OnLongClickListener listener) {
|
|
if (view == null) {
|
|
return;
|
|
}
|
|
if (listener == null) {
|
|
view.setOnLongClickListener(null);
|
|
return;
|
|
}
|
|
view.setOnLongClickListener(new OnLongClickWrapper(listener));
|
|
}
|
|
|
|
@ImplementedInterface(value = "android.view.LayoutInflater$Factory", scope = Scope.LEAF)
|
|
@Insert(mayCreateSuper = true)
|
|
@TargetMethod(methodName = "onCreateView")
|
|
public View onCreateView(String name, Context context, AttributeSet attrs) {
|
|
View view = (View) Origin.call();
|
|
if (view == null && attrs != null) {
|
|
view = tryCreateView(name, context, attrs);
|
|
}
|
|
checkSetBgIfNeed(null, view, attrs);
|
|
return view;
|
|
}
|
|
|
|
@ImplementedInterface(value = "android.view.LayoutInflater$Factory2", scope = Scope.LEAF)
|
|
@Insert(mayCreateSuper = true)
|
|
@TargetMethod(methodName = "onCreateView")
|
|
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
|
|
View view = (View) Origin.call();
|
|
if (view == null && attrs != null) {
|
|
view = tryCreateView(name, context, attrs);
|
|
}
|
|
checkSetBgIfNeed(parent, view, attrs);
|
|
return view;
|
|
}
|
|
|
|
@Nullable
|
|
private static View tryCreateView(String name, Context context, AttributeSet attrs) {
|
|
View ret = null;
|
|
if (name != null && name.length() > 0) {
|
|
if (context != null) {
|
|
try {
|
|
ret = LayoutInflater.from(context).createView(name, null, attrs);
|
|
} catch (ClassNotFoundException ignore) {}
|
|
|
|
if (ret == null) {
|
|
String[] prefixList = {
|
|
"android.widget.",
|
|
"android.webkit.",
|
|
"android.app."
|
|
};
|
|
for (String prefix : prefixList) {
|
|
try {
|
|
ret = LayoutInflater.from(context).createView(name, prefix, attrs);
|
|
} catch (ClassNotFoundException ignore) { }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
private static void checkSetBgIfNeed(View parent, View view, AttributeSet attr) {
|
|
try {
|
|
TypedArray t = null;
|
|
float alpha = 0.6f;
|
|
boolean enabled;
|
|
try {
|
|
if (parent == null && view == null) {
|
|
return;
|
|
}
|
|
Context context = parent == null ? view.getContext() : parent.getContext();
|
|
if (context == null) {
|
|
return;
|
|
}
|
|
|
|
if (parent != null) {
|
|
Object tag = parent.getTag(R.id.click_pressed_attr_enabled);
|
|
if (tag != null && !((Boolean) tag)) {
|
|
view.setTag(R.id.click_pressed_attr_enabled, false);
|
|
return;
|
|
}
|
|
}
|
|
if (view != null) {
|
|
Object tag = view.getTag(R.id.click_pressed_attr_enabled);
|
|
if (tag != null && !((Boolean) tag)) {
|
|
view.setTag(R.id.click_pressed_attr_enabled, false);
|
|
return;
|
|
}
|
|
if (attr != null) {
|
|
t = context.obtainStyledAttributes(attr, R.styleable.ClickPressedStyle);
|
|
enabled = t.getBoolean(R.styleable.ClickPressedStyle_pressed_enabled, true);
|
|
if (!enabled) {
|
|
view.setTag(R.id.click_pressed_attr_enabled, false);
|
|
return;
|
|
}
|
|
alpha = t.getFloat(R.styleable.ClickPressedStyle_pressed_alpha, 0.6f);
|
|
if (alpha < 0.0f || alpha > 1.0f) {
|
|
throw new AssertionError("view custom attr \\'alpha\\' must be in [0.0, 1.0]");
|
|
}
|
|
}
|
|
view.setTag(R.id.click_pressed_attr_id, attr);
|
|
}
|
|
} finally {
|
|
if (t != null) {
|
|
t.recycle();
|
|
}
|
|
}
|
|
if (view instanceof ImageView) {
|
|
ImageView image = (ImageView) view;
|
|
Drawable replaced = checkAndReplaceDrawable(image.getBackground(), alpha);
|
|
if (replaced != null) {
|
|
ViewCompat.setBackground(image, replaced);
|
|
return;
|
|
}
|
|
replaced = checkAndReplaceDrawable(image.getDrawable(), alpha);
|
|
if (replaced != null) {
|
|
image.setImageDrawable(replaced);
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
if (view instanceof TextView) {
|
|
TextView text = (TextView) view;
|
|
Drawable replaced = checkAndReplaceDrawable(text.getBackground(), alpha);
|
|
if (replaced != null) {
|
|
ViewCompat.setBackground(text, replaced);
|
|
return;
|
|
}
|
|
ColorStateList textColor = text.getTextColors();
|
|
if (textColor != null) {
|
|
int pressed = textColor.getColorForState(new int[]{ android.R.attr.state_pressed }, Integer.MIN_VALUE);
|
|
if (pressed != Integer.MIN_VALUE) {
|
|
return;
|
|
}
|
|
int color = textColor.getColorForState(new int[] { android.R.attr.state_enabled }, Integer.MIN_VALUE);
|
|
int defaultColor = color;
|
|
if (color == Integer.MIN_VALUE) {
|
|
defaultColor = textColor.getDefaultColor();
|
|
}
|
|
int pressedColor = Color.argb((int)(Color.alpha(defaultColor) * alpha), Color.red(defaultColor), Color.green(defaultColor), Color.blue(defaultColor));
|
|
int disableColor = textColor.getColorForState(new int[] { -android.R.attr.state_enabled }, Integer.MIN_VALUE);
|
|
int size = 2;
|
|
if (disableColor != Integer.MIN_VALUE) {
|
|
size += 1;
|
|
}
|
|
int[][] states = new int[size][1];
|
|
int[] colors = new int[size];
|
|
states[0] = new int[] { android.R.attr.state_pressed };
|
|
colors[0] = pressedColor;
|
|
if (size > 2) {
|
|
states[1] = new int[] { -android.R.attr.state_enabled };
|
|
colors[1] = disableColor;
|
|
states[2] = new int[] { android.R.attr.state_enabled };
|
|
colors[2] = defaultColor;
|
|
} else {
|
|
states[1] = new int[] { -android.R.attr.state_pressed };
|
|
colors[1] = defaultColor;
|
|
}
|
|
ColorStateList newColor = new ColorStateList(states, colors);
|
|
text.setTextColor(newColor);
|
|
}
|
|
return;
|
|
}
|
|
if (view != null) {
|
|
Drawable replaced = checkAndReplaceDrawable(view.getBackground(), alpha);
|
|
if (replaced != null) {
|
|
ViewCompat.setBackground(view, replaced);
|
|
}
|
|
}
|
|
} catch (Throwable t) {
|
|
t.printStackTrace();
|
|
}
|
|
}
|
|
|
|
private static Drawable checkAndReplaceDrawable(Drawable old, float alpha) {
|
|
if (old == null) {
|
|
return null;
|
|
}
|
|
if (old instanceof StateListDrawable) {
|
|
StateListDrawable drawable = (StateListDrawable) old;
|
|
int[] states = drawable.getState();
|
|
boolean hasPressed = false;
|
|
int index = 0;
|
|
int targetIndex = -1;
|
|
for (int state : states) {
|
|
if (!hasPressed && state == android.R.attr.state_pressed) {
|
|
hasPressed = true;
|
|
}
|
|
if (targetIndex == -1 && state != -android.R.attr.state_enabled) {
|
|
targetIndex = index;
|
|
}
|
|
if (hasPressed && targetIndex != -1) {
|
|
break;
|
|
}
|
|
index ++;
|
|
}
|
|
if (hasPressed) {
|
|
return null;
|
|
}
|
|
if (targetIndex != -1) {
|
|
Drawable origin= null;
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
origin = drawable.getStateDrawable(targetIndex);
|
|
} else {
|
|
origin = drawable.getCurrent();
|
|
}
|
|
if (origin != null) {
|
|
Drawable.ConstantState constantState = origin.getConstantState();
|
|
if (constantState != null) {
|
|
Drawable pressed = DrawableCompat.wrap(constantState.newDrawable().mutate());
|
|
pressed.setAlpha((int)(255 * alpha));
|
|
drawable.addState(new int[] { android.R.attr.state_pressed }, pressed);
|
|
return drawable;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
return null;
|
|
}
|
|
Drawable.ConstantState constantState = old.getConstantState();
|
|
if (constantState != null) {
|
|
StateListDrawable result = new StateListDrawable();
|
|
result.addState(new int[] { -android.R.attr.state_pressed }, old);
|
|
Drawable pressed = DrawableCompat.wrap(constantState.newDrawable().mutate());
|
|
pressed.setAlpha((int)(255 * alpha));
|
|
result.addState(new int[] { android.R.attr.state_pressed }, pressed);
|
|
return result;
|
|
}
|
|
return null;
|
|
}
|
|
}
|