【问题标题】:Constant ripple effect in androidandroid中的恒定涟漪效应
【发布时间】:2020-05-30 20:08:54
【问题描述】:

我想要做的是在 LinearLayout 的背景上产生恒定的涟漪效应。为什么?基本上,此 LinearLayout 表示正在观看此项目的实时用户。因此,我希望背景具有恒定的波纹动画,类似于某些具有实时指示器的应用程序,该指示器在该指示器的背景上具有波纹效果。我希望我的问题很清楚。

示例: 我希望这种效果不断发生

【问题讨论】:

  • 检查我的答案我已经根据您的需要创建了自定义波纹视图。

标签: android android-studio android-layout kotlin android-animation


【解决方案1】:

您好,我尝试编写类似这样的代码,下面是我接近的内容。您可以随时调整数字以减慢动画和其他内容的速度。

1) 在您的 res/drawable 中创建一个名为 temp_ripple.xml

的波纹可绘制背景
<?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) 从视图中获取ripple drawable并创建一个runnable,在2秒后运行,通过在按钮的点击监听器中设置ripple drawable的状态来重复动画

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)
    }

}

【讨论】:

    【解决方案2】:

    花了很多小时后,我根据您的需要为 无限波纹视图 创建了 自定义类,并使用 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-&gt;values-&gt;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>
    

    结果

    希望对你有帮助!

    【讨论】:

      猜你喜欢
      • 2015-07-03
      • 1970-01-01
      • 2019-12-07
      • 1970-01-01
      • 2016-04-25
      • 2015-03-23
      • 1970-01-01
      • 1970-01-01
      • 2021-07-29
      相关资源
      最近更新 更多