Destek kitaplığını kullanarak dalgalanma animasyonu nasıl elde edilir?


171

Düğme tıklamasına bir dalgalanma animasyonu eklemeye çalışıyorum. Aşağıdaki gibi yaptım ama 21 minSdKVersion gerektirir.

ripple.xml

<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="?android:colorControlHighlight">
    <item>
        <shape android:shape="rectangle">
            <solid android:color="?android:colorAccent" />
        </shape>
    </item>
</ripple>

Buton

<com.devspark.robototextview.widget.RobotoButton
    android:id="@+id/loginButton"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/ripple"
    android:text="@string/login_button" />

Tasarım kütüphanesiyle geriye dönük uyumlu hale getirmek istiyorum.

Bu nasıl yapılabilir?

Yanıtlar:


380

Temel dalgalanma kurulumu

  • Görünümde bulunan dalgalanmalar.
    android:background="?selectableItemBackground"

  • Görünümün sınırlarını aşan dalgalanmalar:
    android:background="?selectableItemBackgroundBorderless"

    Java kodunda xml referanslarını çözmek için buraya bir göz atın ?(attr).

Destek Kütüphanesi

  • Kullanılması ?attr:(veya ?yerine steno) ?android:attrreferanslar destek kütüphane , böylece API 7'ye uygun geri döndü.

Görüntüler / arka planlar ile dalgalanmalar

  • Bir resim veya altyapıya sahip ve en kolay çözüm dalgalanma bindirilirken sarılmasıdır için Viewbir yer FrameLayoutile dalgalanma set ile setForeground()ya setBackground().

Dürüst olmak gerekirse bunu yapmanın temiz bir yolu yoktur.


38
Bu, 21'den önceki sürümlere dalgalanma desteği eklemez.
AndroidDev

21
Dalgalanma desteği eklemeyebilir, ancak bu çözüm güzelce bozunur. Bu aslında yaşadığım sorunu çözdü. L üzerinde bir dalgalanma etkisi ve android'in önceki sürümünde basit bir seçim istedim.
Dave Jensen

4
@AndroidDev, @Dave Jensen: Aslında, v7 destek kitaplığını referanslar ?attr:yerine kullanarak, ?android:attronu kullandığınızı varsayarak, API 7'ye geriye dönük uyumluluk sağlar. Bkz . Developer.android.com/tools/support-library/features. html # v7
Ben De La Haye

14
Arka plan rengim de olmasını istersem ne olur?
stanley santoso

9
Dalgalanma efekti API <21 için DEĞİLDİR. Dalgalanma malzeme tasarımının bir tıklama efektidir. Google Tasarım Ekibi perspektifi, lolipop öncesi cihazlarda gösterilmez. lolipop öncesi kendi tıklama efektlerine sahiptir (varsayılan olarak açık mavi kapak). Sunulan cevap, sistemin varsayılan tıklama efektini kullanmanızı önerir. Tıklama efektinin renklerini özelleştirmek istiyorsanız, bir çekilebilir yapmanız ve dalgalanma tıklama efekti için (<ripple> çizilebilir ile) res / drawable-v21'e ve olmayanlar için res / drawable'a yerleştirmeniz gerekir. dalgalanma tıklama efekti (genellikle <selector> çizilebilir ile)
nbtk

55

Daha önce bu soruyu konu dışı olarak kapatmaya oy verdim ama aslında fikrimi değiştirdim, çünkü bu ne yazık ki henüz destek kütüphanesinin bir parçası olmayan oldukça güzel bir görsel efekt. Büyük olasılıkla gelecekteki güncellemede görünecek, ancak hiçbir zaman çerçevesi açıklanmadı.

Neyse ki, zaten birkaç özel uygulama var:

Android'in eski sürümleriyle uyumlu Materlial temalı widget setleri dahil:

böylece bunlardan birini deneyebilir veya diğer "materyal widget'ları" için google veya benzeri ...


12
Bu şimdi destek kütüphanesinin bir parçası, cevabımı görün.
Ben De La Haye

Teşekkürler! İkinci lib'i kullandım, birincisi yavaş telefonlarda çok yavaştı.
Ferran Maylinch

27

Dalgalanma düğmeleri yapan basit bir sınıf yaptım, sonunda hiç ihtiyaç duymadım, bu yüzden en iyisi değil, Ama işte burada:

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Button;

public class RippleView extends Button
{
    private float duration = 250;

    private float speed = 1;
    private float radius = 0;
    private Paint paint = new Paint();
    private float endRadius = 0;
    private float rippleX = 0;
    private float rippleY = 0;
    private int width = 0;
    private int height = 0;
    private OnClickListener clickListener = null;
    private Handler handler;
    private int touchAction;
    private RippleView thisRippleView = this;

    public RippleView(Context context)
    {
        this(context, null, 0);
    }

    public RippleView(Context context, AttributeSet attrs)
    {
        this(context, attrs, 0);
    }

    public RippleView(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init()
    {
        if (isInEditMode())
            return;

        handler = new Handler();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.WHITE);
        paint.setAntiAlias(true);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = h;
    }

    @Override
    protected void onDraw(@NonNull Canvas canvas)
    {
        super.onDraw(canvas);

        if(radius > 0 && radius < endRadius)
        {
            canvas.drawCircle(rippleX, rippleY, radius, paint);
            if(touchAction == MotionEvent.ACTION_UP)
                invalidate();
        }
    }

    @Override
    public boolean onTouchEvent(@NonNull MotionEvent event)
    {
        rippleX = event.getX();
        rippleY = event.getY();

        switch(event.getAction())
        {
            case MotionEvent.ACTION_UP:
            {
                getParent().requestDisallowInterceptTouchEvent(false);
                touchAction = MotionEvent.ACTION_UP;

                radius = 1;
                endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY);
                speed = endRadius / duration * 10;
                handler.postDelayed(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        if(radius < endRadius)
                        {
                            radius += speed;
                            paint.setAlpha(90 - (int) (radius / endRadius * 90));
                            handler.postDelayed(this, 1);
                        }
                        else
                        {
                            clickListener.onClick(thisRippleView);
                        }
                    }
                }, 10);
                invalidate();
                break;
            }
            case MotionEvent.ACTION_CANCEL:
            {
                getParent().requestDisallowInterceptTouchEvent(false);
                touchAction = MotionEvent.ACTION_CANCEL;
                radius = 0;
                invalidate();
                break;
            }
            case MotionEvent.ACTION_DOWN:
            {
                getParent().requestDisallowInterceptTouchEvent(true);
                touchAction = MotionEvent.ACTION_UP;
                endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY);
                paint.setAlpha(90);
                radius = endRadius/4;
                invalidate();
                return true;
            }
            case MotionEvent.ACTION_MOVE:
            {
                if(rippleX < 0 || rippleX > width || rippleY < 0 || rippleY > height)
                {
                    getParent().requestDisallowInterceptTouchEvent(false);
                    touchAction = MotionEvent.ACTION_CANCEL;
                    radius = 0;
                    invalidate();
                    break;
                }
                else
                {
                    touchAction = MotionEvent.ACTION_MOVE;
                    invalidate();
                    return true;
                }
            }
        }

        return false;
    }

    @Override
    public void setOnClickListener(OnClickListener l)
    {
        clickListener = l;
    }
}

DÜZENLE

Birçok insan böyle bir şey aradığından, diğer görünümlerin dalgalanma etkisine sahip olabileceği bir sınıf yaptım:

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

public class RippleViewCreator extends FrameLayout
{
    private float duration = 150;
    private int frameRate = 15;

    private float speed = 1;
    private float radius = 0;
    private Paint paint = new Paint();
    private float endRadius = 0;
    private float rippleX = 0;
    private float rippleY = 0;
    private int width = 0;
    private int height = 0;
    private Handler handler = new Handler();
    private int touchAction;

    public RippleViewCreator(Context context)
    {
        this(context, null, 0);
    }

    public RippleViewCreator(Context context, AttributeSet attrs)
    {
        this(context, attrs, 0);
    }

    public RippleViewCreator(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init()
    {
        if (isInEditMode())
            return;

        paint.setStyle(Paint.Style.FILL);
        paint.setColor(getResources().getColor(R.color.control_highlight_color));
        paint.setAntiAlias(true);

        setWillNotDraw(true);
        setDrawingCacheEnabled(true);
        setClickable(true);
    }

    public static void addRippleToView(View v)
    {
        ViewGroup parent = (ViewGroup)v.getParent();
        int index = -1;
        if(parent != null)
        {
            index = parent.indexOfChild(v);
            parent.removeView(v);
        }
        RippleViewCreator rippleViewCreator = new RippleViewCreator(v.getContext());
        rippleViewCreator.setLayoutParams(v.getLayoutParams());
        if(index == -1)
            parent.addView(rippleViewCreator, index);
        else
            parent.addView(rippleViewCreator);
        rippleViewCreator.addView(v);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = h;
    }

    @Override
    protected void dispatchDraw(@NonNull Canvas canvas)
    {
        super.dispatchDraw(canvas);

        if(radius > 0 && radius < endRadius)
        {
            canvas.drawCircle(rippleX, rippleY, radius, paint);
            if(touchAction == MotionEvent.ACTION_UP)
                invalidate();
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event)
    {
        return true;
    }

    @Override
    public boolean onTouchEvent(@NonNull MotionEvent event)
    {
        rippleX = event.getX();
        rippleY = event.getY();

        touchAction = event.getAction();
        switch(event.getAction())
        {
            case MotionEvent.ACTION_UP:
            {
                getParent().requestDisallowInterceptTouchEvent(false);

                radius = 1;
                endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY);
                speed = endRadius / duration * frameRate;
                handler.postDelayed(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        if(radius < endRadius)
                        {
                            radius += speed;
                            paint.setAlpha(90 - (int) (radius / endRadius * 90));
                            handler.postDelayed(this, frameRate);
                        }
                        else if(getChildAt(0) != null)
                        {
                            getChildAt(0).performClick();
                        }
                    }
                }, frameRate);
                break;
            }
            case MotionEvent.ACTION_CANCEL:
            {
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
            }
            case MotionEvent.ACTION_DOWN:
            {
                getParent().requestDisallowInterceptTouchEvent(true);
                endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY);
                paint.setAlpha(90);
                radius = endRadius/3;
                invalidate();
                return true;
            }
            case MotionEvent.ACTION_MOVE:
            {
                if(rippleX < 0 || rippleX > width || rippleY < 0 || rippleY > height)
                {
                    getParent().requestDisallowInterceptTouchEvent(false);
                    touchAction = MotionEvent.ACTION_CANCEL;
                    break;
                }
                else
                {
                    invalidate();
                    return true;
                }
            }
        }
        invalidate();
        return false;
    }

    @Override
    public final void addView(@NonNull View child, int index, ViewGroup.LayoutParams params)
    {
        //limit one view
        if (getChildCount() > 0)
        {
            throw new IllegalStateException(this.getClass().toString()+" can only have one child.");
        }
        super.addView(child, index, params);
    }
}

else if (clickListener! = null) {clickListener.onClick (thisRippleView); }
Volodymyr Kulyk

Uygulaması basit ... tak ve çalıştır :)
Ranjith Kumar

Bu sınıfı RecyclerView her görünümü üzerinde kullanırsanız ClassCastException alıyorum.
Ali_Waris

1
@Ali_Waris Destek kütüphanesi bugünlerde dalgalanmalarla başa çıkabilir, ancak bunu düzeltmek addRippleToViewiçin dalgalanma efekti eklemek yerine tek yapmanız gereken yapmaktır . Oldukça her görünümünde yapmak RecyclerViewaRippleViewCreator
Nicolas Tyler

17

Bazen özel bir arka planınız olur, bu durumda daha iyi bir çözüm kullanmaktır android:foreground="?selectableItemBackground"


2
Evet, ancak API> = 23'te veya 21 API'ye sahip cihazlarda çalışır, ancak yalnızca CardView veya FrameLayout'ta
Skullper

17

Çok basit ;-)

Öncelikle biri eski api sürümü için diğeri de en yeni sürüm için iki tane çekilebilir dosya oluşturmalısınız. yeni api sürümü android studio için çizilebilir dosya oluşturursanız otomatik olarak eski bir oluşturmanızı öneririz. ve son olarak bu çekilebilir resmi arka plan görünümünüze ayarlayın.

Yeni api sürümü için örnek çekilebilir (res / drawable-v21 / ripple.xml):

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="?android:colorControlHighlight">
    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/colorPrimary" />
            <corners android:radius="@dimen/round_corner" />
        </shape>
    </item>
</ripple>

Eski API sürümü için örnek çizilebilir (res / drawable / ripple.xml)

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="@color/colorPrimary" />
    <corners android:radius="@dimen/round_corner" />
</shape>

Çekilebilir dalgalanma hakkında daha fazla bilgi için şu adresi ziyaret edin: https://developer.android.com/reference/android/graphics/drawable/RippleDrawable.html


1
Gerçekten çok basit!
Aditya S.

Bu çözüm kesinlikle çok daha fazla değerlendirilmelidir! Teşekkür ederim.
JerabekJakub

0

bazen bu satırı herhangi bir düzende veya bileşende kullanabilir.

 android:background="?attr/selectableItemBackground"

Gibi.

 <RelativeLayout
                android:id="@+id/relative_ticket_checkin"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:background="?attr/selectableItemBackground">
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.