Як досягти пульсації анімації за допомогою бібліотеки підтримки?


171

Я намагаюся додати пульсацію анімації при натисканні кнопки. Мені подобалося нижче, але для цього потрібно minSdKVersion до 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>

Кнопка

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

Я хочу зробити це назад сумісним з бібліотекою дизайну.

Як це можна зробити?

Відповіді:


380

Основна настройка пульсацій

  • Пульсації, що містяться в представленні.
    android:background="?selectableItemBackground"

  • Пульсації, що виходять за межі перегляду:
    android:background="?selectableItemBackgroundBorderless"

    Подивіться тут, як вирішити ?(attr)xml посилання в коді Java.

Бібліотека підтримки

  • Використання ?attr:(або ?скорочення) замість ?android:attrпосилань на бібліотеку підтримки , тому доступна назад до API 7.

Брижі з зображеннями / фоном

  • Щоб зображення або фон і пульсація накладених найпростішим рішенням, це загортати Viewв FrameLayoutрядок з пульсацією набір з setForeground()або setBackground().

Чесно кажучи, немає чистого способу зробити це інакше.


38
Це не додає підтримки для пульсацій до версій до 21 року.
AndroidDev

21
Він може не додавати підтримку пульсацій, але це рішення добре погіршує. Це фактично вирішило конкретне питання, яке у мене виникло. Я хотів ефект пульсації на L та простий вибір на попередній версії Android.
Дейв Йенсен

4
@AndroidDev, @Dave Jensen: Власне, використовуючи ?attr:замість ?android:attrпосилань бібліотеку підтримки v7, яка, припускаючи, що ви її використовуєте, дає вам зворотну сумісність з API 7. Дивіться: developer.android.com/tools/support-library/features. html # v7
Ben De La Haye

14
Що робити, якщо я теж хочу мати колір тла?
Stanley santoso

9
Ефект Ripple НЕ призначений для API <21. Ripple є ефектом клацання дизайну матеріалу. Перспектива команди дизайнера Google не відображає її на пристроях, що попередньо льодяники. pre-lolipop мають свої власні ефекти клацання (за замовчуванням до світло-блакитної обкладинки). Запропонована відповідь пропонує використовувати типовий ефект натискання системи за замовчуванням. Якщо ви хочете налаштувати кольори ефекту клацання, вам потрібно зробити малюнок і розмістити його на res / dravable-v21 для ефекту пульсації кліку (з <ripple>, що малюється), і в res / dravable для не- ефект пульсації клацання (звичайно з <selector>
малюванням

55

Я раніше проголосував за те, щоб закрити це питання поза темою, але насправді я передумав, оскільки це дуже приємний візуальний ефект, який, на жаль, ще не є частиною бібліотеки підтримки. Він, швидше за все, з’явиться в майбутньому оновлення, але не оголошено часових рамок.

На щастя, вже є кілька спеціальних реалізацій:

включаючи набори тематичних віджетів Materlial, сумісні зі старими версіями Android:

тож ви можете спробувати один із них або Google для інших "матеріальних віджетів" тощо.


12
Зараз це частина бібліотеки підтримки, дивіться мою відповідь.
Бен Де Ла Хей

Дякую! Я використовував другу вкладку , перша була занадто повільною у повільних телефонах.
Ферран Мейлінч

27

Я створив простий клас, який робить пульсації кнопок, мені ніколи не потрібні були, зрештою, це не найкраще, але ось це:

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

Оскільки багато людей шукають щось подібне, я створив клас, який може зробити так, щоб інші погляди мали ефект пульсацій:

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

інакше якщо (clickListener! = null) {clickListener.onClick (thisRippleView); }
Володимир Кулик

Простий у здійсненні ... plug and play :)
Ранджіт Кумар

Я отримую ClassCastException, якщо використовую цей клас для кожного перегляду RecyclerView.
Ali_Waris

1
@Ali_Waris Бібліотека підтримки може вирішувати пульсації в наші дні, але виправити це все, що вам потрібно зробити, це замість використання addRippleToViewефекту пульсацій. Швидше за все, щоб кожен вид в RecyclerViewаRippleViewCreator
Ніколас Тайлер

17

Іноді у вас є власне передумови, тоді найкращим рішенням є використання android:foreground="?selectableItemBackground"


2
Так, але це працює на API> = 23 або на пристроях із 21 API, але лише у CardView або FrameLayout
Skullper

17

Це дуже просто ;-)

Спочатку ви повинні створити два файли, що витягуються: один для старої версії api та ще один для новітньої версії, звичайно! якщо ви створюєте файл, що витягується, для новітньої версії api для андроїд-студії, вам пропонується створити старий автоматично. і, нарешті, встановіть цей малюнок для перегляду фону

Зразок для малювання для нової версії api (res / dravable-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>

Зразок для малювання для старої версії api (res / dravable / 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>

Щоб отримати докладнішу інформацію про пульсацію, що малюється, просто відвідайте це: https://developer.android.com/reference/android/graphics/dravable/RippleDravable.html


1
Це дійсно дуже просто!
Адітя С.

Це рішення, безумовно, має бути набагато більш прихильним! Дякую.
JerabekJakub

0

інколи буде корисно використовувати цей рядок на будь-якому макеті або компонентах.

 android:background="?attr/selectableItemBackground"

Як ми.

 <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">
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.