Bagaimana cara mencapai animasi riak menggunakan perpustakaan dukungan?


171

Saya mencoba menambahkan animasi riak di klik tombol. Saya memang suka di bawah ini tetapi membutuhkan minSdKVersion ke 21.

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>

Tombol

<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" />

Saya ingin membuatnya kompatibel dengan perpustakaan desain.

Bagaimana ini bisa dilakukan?

Jawaban:


380

Pengaturan riak dasar

  • Riak yang terkandung dalam tampilan.
    android:background="?selectableItemBackground"

  • Riak yang melampaui batas tampilan:
    android:background="?selectableItemBackgroundBorderless"

    Lihat di sini untuk menyelesaikan ?(attr)referensi xml dalam kode Java.

Perpustakaan pendukung

  • Menggunakan ?attr:(atau ?singkatan) alih-alih ?android:attrreferensi perpustakaan dukungan , jadi tersedia kembali ke API 7.

Riak dengan gambar / latar belakang

  • Untuk memiliki citra atau latar belakang dan overlay riak solusi termudah adalah dengan membungkus Viewdalam FrameLayoutdengan set riak dengan setForeground()atau setBackground().

Jujur saja tidak ada cara yang bersih untuk melakukan ini sebaliknya.


38
Ini tidak menambahkan dukungan riak ke versi sebelum 21.
AndroidDev

21
Itu mungkin tidak menambahkan dukungan riak tetapi solusi ini menurun dengan baik. Ini sebenarnya memecahkan masalah khusus yang saya alami. Saya ingin efek riak pada L dan pilih sederhana pada versi Android sebelumnya.
Dave Jensen

4
@AndroidDev, @Dave Jensen: Sebenarnya, dengan menggunakan ?attr:alih - alih ?android:attrreferensi pustaka dukungan v7, yang, dengan asumsi Anda menggunakannya, memberi Anda kompatibilitas ke belakang ke API 7. Lihat: developer.android.com/tools/support-library/features. html # v7
Ben De La Haye

14
Bagaimana jika saya juga ingin memiliki warna latar belakang?
stanley santoso

9
Efek riak TIDAK dimaksudkan untuk API <21. Riak adalah efek klik dari desain material. Perspektif Tim Desain Google tidak menunjukkannya di perangkat pra-lolipop. pre-lolipop memiliki efek klik sendiri (default ke sampul biru muda). Jawaban yang ditawarkan menyarankan untuk menggunakan efek klik-default sistem. Jika Anda ingin menyesuaikan warna efek klik, Anda perlu membuat drawable dan meletakkannya di res / drawable-v21 untuk efek klik ripple (dengan drawable <ripple>), dan pada res / drawable untuk non- efek klik riak (dengan <selektor> biasanya dapat
digambar

55

Saya sebelumnya memilih untuk menutup pertanyaan ini sebagai di luar topik tetapi sebenarnya saya berubah pikiran karena ini adalah efek visual yang cukup bagus, yang sayangnya, belum menjadi bagian dari perpustakaan dukungan. Kemungkinan besar akan muncul di pembaruan mendatang, tetapi tidak ada kerangka waktu yang diumumkan.

Untungnya ada beberapa implementasi khusus yang sudah tersedia:

termasuk set widget bertema Materlial yang kompatibel dengan versi Android yang lebih lama:

sehingga Anda dapat mencoba salah satu dari ini atau google untuk "widget materi" lainnya atau lebih ...


12
Ini sekarang bagian dari perpustakaan dukungan, lihat jawaban saya.
Ben De La Haye

Terima kasih! Saya menggunakan lib kedua , yang pertama terlalu lambat di ponsel lambat.
Ferran Maylinch

27

Saya membuat kelas sederhana yang membuat tombol riak, saya tidak pernah membutuhkannya pada akhirnya jadi ini bukan yang terbaik, Tapi ini dia:

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

EDIT

Karena banyak orang mencari sesuatu seperti ini, saya membuat kelas yang dapat membuat pandangan lain memiliki efek riak:

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

lain jika (clickListener! = null) {clickListener.onClick (thisRippleView); }
Volodymyr Kulyk

Mudah diterapkan ... plug & play :)
Ranjith Kumar

Saya mendapatkan ClassCastException jika saya menggunakan kelas ini pada setiap tampilan RecyclerView.
Ali_Waris

1
@Ali_Waris Perpustakaan dukungan dapat menangani riak hari ini tetapi untuk memperbaikinya yang harus Anda lakukan adalah, alih-alih menggunakan addRippleToViewuntuk menambahkan efek riak. Alih-alih membuat setiap tampilan di RecyclerViewaRippleViewCreator
Nicolas Tyler

17

Terkadang Anda memiliki latar belakang khusus, dalam hal ini solusi yang lebih baik digunakan android:foreground="?selectableItemBackground"


2
Ya, tetapi berfungsi pada API> = 23 atau pada perangkat dengan 21 API, tetapi hanya di CardView atau FrameLayout
Skullper

17

Ini sangat sederhana ;-)

Pertama, Anda harus membuat dua file yang dapat digambar satu untuk versi api lama dan satu lagi untuk versi terbaru, Tentu saja! jika Anda membuat file yang dapat ditarik untuk android versi api terbaru studio menyarankan Anda untuk membuat yang lama secara otomatis. dan akhirnya atur drawable ini ke tampilan latar belakang Anda.

Sampel dapat ditarik untuk versi api baru (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>

Sampel dapat ditarik untuk versi api lama (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>

Untuk info lebih lanjut tentang ripple drawable, kunjungi ini: https://developer.android.com/reference/android/graphics/drawable/RippleDrawable.html


1
Benar-benar sangat sederhana!
Aditya S.

Solusi ini pasti jauh lebih tervotasikan! Terima kasih.
JerabekJakub

0

terkadang akan digunakan baris ini pada tata letak atau komponen apa pun.

 android:background="?attr/selectableItemBackground"

Seperti.

 <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">
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.