我想做的是在LinearLayout的背景上产生恒定的波纹效果。为什么?基本上,此LinearLayout指示实时用户正在观看此项目。因此,我希望背景具有恒定的波纹动画,类似于某些具有实时指示器且该指示器的背景具有波纹效果的应用程序。我希望我的问题很清楚。
示例:我希望这种效果不断出现
嗨,我尝试编写类似这样的代码,下面是我所接近的。您可以随时输入周数以减慢动画和其他操作的速度。
1)在名为temp_ripple.xml的res / drawable
中创建波纹可绘制背景。<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/colorPrimary"
>
<item android:id="@android:id/mask"
android:drawable="@android:color/holo_green_dark"
>
</item>
<item
android:drawable="@android:color/holo_orange_dark">
</item>
</ripple>
2)将背景分配给可能的视图候选对象,如下所示,此处AppCompatButton为android:background="@drawable/temp_ripple"
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnLive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_gravity="center"
android:background="@drawable/temp_ripple"
android:foreground="?selectableItemBackground"
android:text="12.5k Live"
android:textColor="@android:color/white" />
</RelativeLayout>
3)从视图中获取波纹可绘制,并在2秒后创建可运行的运行,以通过在按钮的单击侦听器中设置波纹可绘制的状态来重复动画。
package com.example.android.treasureHunt
import android.content.res.ColorStateList
import android.graphics.drawable.RippleDrawable
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.MotionEvent
import android.view.View
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.temp_activity.*
class TempActivity : AppCompatActivity(R.layout.temp_activity) {
val handler = Handler()
lateinit var runnable: Runnable
var count = 0
lateinit var rippleDrawable: RippleDrawable
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
rippleDrawable = btnLive.background as RippleDrawable
setLiveCountListener()
btnLive.setOnClickListener {
Log.d("TAG++", "button clicked")
rippleDrawable.state = intArrayOf(
android.R.attr.state_pressed,
android.R.attr.state_enabled
)
}
}
private fun setLiveCountListener() {
runnable = Runnable {
rippleDrawable.state = intArrayOf()
btnLive.performClick()
//to perform another runnable after some time creating a race condition
handler.postDelayed(runnable, 2000)
//condition to breakout from loop
if (count == 10) {
handler.removeCallbacks(runnable)
}
Log.d("TAG++", "Loop running")
}
//trigger the start of the ui thread
handler.postDelayed(runnable, 2000)
}
}
花费大量时间后,根据需要使用this lib进行自定义,为无限波纹视图创建了[[自定义类。
InfiniteRippleLayout
public class InfiniteRippleLayout extends FrameLayout {
/**
* Author:Hardik Talaviya
* Date: 2020.02.15 1:30 PM
* Describe:
*/
private static final int DEFAULT_DURATION = 350;
private static final int DEFAULT_FADE_DURATION = 75;
private static final float DEFAULT_ALPHA = 0.2f;
private static final int DEFAULT_COLOR = Color.BLACK;
private static final int DEFAULT_BACKGROUND = Color.TRANSPARENT;
private static final boolean DEFAULT_DELAY_CLICK = true;
private static final boolean DEFAULT_PERSISTENT = false;
private static final boolean DEFAULT_SEARCH_ADAPTER = false;
private static final boolean DEFAULT_RIPPLE_OVERLAY = false;
private static final int DEFAULT_ROUNDED_CORNERS = 0;
private static final int FADE_EXTRA_DELAY = 50;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Rect bounds = new Rect();
private int rippleColor;
private boolean rippleOverlay;
private int rippleDuration;
private int rippleAlpha;
private boolean rippleDelayClick;
private int rippleFadeDuration;
private boolean ripplePersistent;
private Drawable rippleBackground;
private boolean rippleInAdapter;
private float rippleRoundedCorners;
private float radius;
private AdapterView parentAdapter;
private View childView;
private AnimatorSet rippleAnimator;
private Point currentCoords = new Point();
private int layerType;
private int positionInAdapter;
/*
* Animations
*/
private Property<InfiniteRippleLayout, Float> radiusProperty
= new Property<InfiniteRippleLayout, Float>(Float.class, "radius") {
@Override
public Float get(InfiniteRippleLayout object) {
return object.getRadius();
}
@Override
public void set(InfiniteRippleLayout object, Float value) {
object.setRadius(value);
}
};
private Property<InfiniteRippleLayout, Integer> circleAlphaProperty
= new Property<InfiniteRippleLayout, Integer>(Integer.class, "rippleAlpha") {
@Override
public Integer get(InfiniteRippleLayout object) {
return object.getRippleAlpha();
}
@Override
public void set(InfiniteRippleLayout object, Integer value) {
object.setRippleAlpha(value);
}
};
public InfiniteRippleLayout(Context context) {
this(context, null, 0);
}
public InfiniteRippleLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public InfiniteRippleLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setWillNotDraw(false);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.InfiniteRippleLayout);
rippleColor = a.getColor(R.styleable.InfiniteRippleLayout_mrl_rippleColor, DEFAULT_COLOR);
rippleOverlay = a.getBoolean(R.styleable.InfiniteRippleLayout_mrl_rippleOverlay, DEFAULT_RIPPLE_OVERLAY);
rippleDuration = a.getInt(R.styleable.InfiniteRippleLayout_mrl_rippleDuration, DEFAULT_DURATION);
rippleAlpha = (int) (255 * a.getFloat(R.styleable.InfiniteRippleLayout_mrl_rippleAlpha, DEFAULT_ALPHA));
rippleDelayClick = a.getBoolean(R.styleable.InfiniteRippleLayout_mrl_rippleDelayClick, DEFAULT_DELAY_CLICK);
rippleFadeDuration = a.getInteger(R.styleable.InfiniteRippleLayout_mrl_rippleFadeDuration, DEFAULT_FADE_DURATION);
rippleBackground = new ColorDrawable(a.getColor(R.styleable.InfiniteRippleLayout_mrl_rippleBackground, DEFAULT_BACKGROUND));
ripplePersistent = a.getBoolean(R.styleable.InfiniteRippleLayout_mrl_ripplePersistent, DEFAULT_PERSISTENT);
rippleInAdapter = a.getBoolean(R.styleable.InfiniteRippleLayout_mrl_rippleInAdapter, DEFAULT_SEARCH_ADAPTER);
rippleRoundedCorners = a.getDimensionPixelSize(R.styleable.InfiniteRippleLayout_mrl_rippleRoundedCorners, DEFAULT_ROUNDED_CORNERS);
a.recycle();
paint.setColor(rippleColor);
paint.setAlpha(rippleAlpha);
enableClipPathSupportIfNecessary();
startRipple();
}
@Override
public final void addView(View child, int index, ViewGroup.LayoutParams params) {
if (getChildCount() > 0) {
throw new IllegalStateException("MaterialRippleLayout can host only one child");
}
//noinspection unchecked
childView = child;
super.addView(child, index, params);
}
@Override
public void setOnClickListener(OnClickListener onClickListener) {
if (childView == null) {
throw new IllegalStateException("MaterialRippleLayout must have a child view to handle clicks");
}
childView.setOnClickListener(onClickListener);
}
@Override
public void setOnLongClickListener(OnLongClickListener onClickListener) {
if (childView == null) {
throw new IllegalStateException("MaterialRippleLayout must have a child view to handle clicks");
}
childView.setOnLongClickListener(onClickListener);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return !findClickableViewInChild(childView, (int) event.getX(), (int) event.getY());
}
private void startRipple() {
float endRadius = getEndRadius();
cancelAnimations();
rippleAnimator = new AnimatorSet();
rippleAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (!ripplePersistent) {
setRadius(0);
setRippleAlpha(rippleAlpha);
}
if (rippleDelayClick) {
startRipple();
}
childView.setPressed(false);
}
});
ObjectAnimator ripple = ObjectAnimator.ofFloat(this, radiusProperty, radius, endRadius);
ripple.setDuration(rippleDuration);
ripple.setInterpolator(new DecelerateInterpolator());
ObjectAnimator fade = ObjectAnimator.ofInt(this, circleAlphaProperty, rippleAlpha, 0);
fade.setDuration(rippleFadeDuration);
fade.setInterpolator(new AccelerateInterpolator());
fade.setStartDelay(rippleDuration - rippleFadeDuration - FADE_EXTRA_DELAY);
if (ripplePersistent) {
rippleAnimator.play(ripple);
} else if (getRadius() > endRadius) {
fade.setStartDelay(0);
rippleAnimator.play(fade);
} else {
rippleAnimator.playTogether(ripple, fade);
}
rippleAnimator.start();
}
private void cancelAnimations() {
if (rippleAnimator != null) {
rippleAnimator.cancel();
rippleAnimator.removeAllListeners();
}
}
private float getEndRadius() {
final int width = getWidth();
final int height = getHeight();
final int halfWidth = width / 2;
final int halfHeight = height / 2;
final float radiusX = halfWidth > currentCoords.x ? width - currentCoords.x : currentCoords.x;
final float radiusY = halfHeight > currentCoords.y ? height - currentCoords.y : currentCoords.y;
return (float) Math.sqrt(Math.pow(radiusX, 2) + Math.pow(radiusY, 2)) * 1.2f;
}
private AdapterView findParentAdapterView() {
if (parentAdapter != null) {
return parentAdapter;
}
ViewParent current = getParent();
while (true) {
if (current instanceof AdapterView) {
parentAdapter = (AdapterView) current;
return parentAdapter;
} else {
try {
current = current.getParent();
} catch (NullPointerException npe) {
throw new RuntimeException("Could not find a parent AdapterView");
}
}
}
}
private boolean adapterPositionChanged() {
if (rippleInAdapter) {
int newPosition = findParentAdapterView().getPositionForView(InfiniteRippleLayout.this);
final boolean changed = newPosition != positionInAdapter;
positionInAdapter = newPosition;
if (changed) {
cancelAnimations();
childView.setPressed(false);
setRadius(0);
}
return changed;
}
return false;
}
private boolean findClickableViewInChild(View view, int x, int y) {
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
final Rect rect = new Rect();
child.getHitRect(rect);
final boolean contains = rect.contains(x, y);
if (contains) {
return findClickableViewInChild(child, x - rect.left, y - rect.top);
}
}
} else if (view != childView) {
return (view.isEnabled() && (view.isClickable() || view.isLongClickable() || view.isFocusableInTouchMode()));
}
return view.isFocusableInTouchMode();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
bounds.set(0, 0, w, h);
rippleBackground.setBounds(bounds);
}
@Override
public boolean isInEditMode() {
return true;
}
/*
* Drawing
*/
@Override
public void draw(Canvas canvas) {
final boolean positionChanged = adapterPositionChanged();
currentCoords = new Point(getWidth() / 2, getHeight() / 2);
if (rippleOverlay) {
if (!positionChanged) {
rippleBackground.draw(canvas);
}
super.draw(canvas);
if (!positionChanged) {
if (rippleRoundedCorners != 0) {
Path clipPath = new Path();
RectF rect = new RectF(0, 0, canvas.getWidth(), canvas.getHeight());
clipPath.addRoundRect(rect, rippleRoundedCorners, rippleRoundedCorners, Path.Direction.CW);
canvas.clipPath(clipPath);
}
canvas.drawCircle(currentCoords.x, currentCoords.y, radius, paint);
}
} else {
if (!positionChanged) {
rippleBackground.draw(canvas);
canvas.drawCircle(currentCoords.x, currentCoords.y, radius, paint);
}
super.draw(canvas);
}
}
private float getRadius() {
return radius;
}
public void setRadius(float radius) {
this.radius = radius;
invalidate();
}
public int getRippleAlpha() {
return paint.getAlpha();
}
public void setRippleAlpha(Integer rippleAlpha) {
paint.setAlpha(rippleAlpha);
invalidate();
}
/**
* {@link Canvas#clipPath(Path)} is not supported in hardware accelerated layers
* before API 18. Use software layer instead
* <p/>
* https://developer.android.com/guide/topics/graphics/hardware-accel.html#unsupported
*/
private void enableClipPathSupportIfNecessary() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (rippleRoundedCorners != 0) {
layerType = getLayerType();
setLayerType(LAYER_TYPE_SOFTWARE, null);
} else {
setLayerType(layerType, null);
}
}
}
}
在attributes.xml
res->values->attributes.xml
中添加此属性<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="InfiniteRippleLayout">
<attr name="mrl_rippleColor" format="color" localization="suggested" />
<attr name="mrl_rippleOverlay" format="boolean" localization="suggested" />
<attr name="mrl_rippleAlpha" format="float" localization="suggested" />
<attr name="mrl_rippleDuration" format="integer" localization="suggested" />
<attr name="mrl_rippleFadeDuration" format="integer" localization="suggested" />
<attr name="mrl_rippleBackground" format="color" localization="suggested" />
<attr name="mrl_rippleDelayClick" format="boolean" localization="suggested" />
<attr name="mrl_ripplePersistent" format="boolean" localization="suggested" />
<attr name="mrl_rippleInAdapter" format="boolean" localization="suggested" />
<attr name="mrl_rippleRoundedCorners" format="dimension" localization="suggested" />
</declare-styleable>
</resources>
使用下面的xml代码添加效果
<com.broooapps.curvegraphview.InfiniteRippleLayout
android:layout_width="match_parent"
android:layout_height="150dp"
app:mrl_rippleAlpha="0.2"
app:mrl_rippleColor="#585858"
app:mrl_rippleDelayClick="true"
app:mrl_rippleDuration="1100"
app:mrl_rippleOverlay="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="HARDIK TALAVIYA"
android:textColor="#000"
android:textSize="20sp" />
</com.broooapps.curvegraphview.InfiniteRippleLayout>
结果
我希望这可以帮助您!