programing

지원 라이브러리를 이용하여 리플 애니메이션을 달성하는 방법은?

cafebook 2023. 11. 4. 13:17
반응형

지원 라이브러리를 이용하여 리플 애니메이션을 달성하는 방법은?

버튼 클릭 시 리플 애니메이션을 추가하려고 합니다.아래와 같이 했는데 minSdK 버전이 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" />

디자인 라이브러리와 역호환이 가능하도록 하고 싶습니다.

어떻게 할 수 있죠?

기본 리플 설정

  • 보기에 포함된 잔물결입니다.
    android:background="?selectableItemBackground"

  • 보기 범위를 벗어나는 잔물결:
    android:background="?selectableItemBackgroundBorderless"

    여기에서 해결 방법을 살펴봅니다.?(attr)자바 코드의 xml 참조.

지원 라이브러리

  • 사용.?attr:(또는 더?속기로 대신하여?android:attr에서는 지원 라이브러리를 참조하므로 API 7로 다시 사용할 수 있습니다.

이미지/배경 잔물결

  • 이미지 또는 배경과 오버레이 리플을 가지는 가장 쉬운 해결책은 다음을 감싸는 것입니다.View일순간에FrameLayout잔물결로setForeground()아니면setBackground().

솔직히 그렇지 않으면 이것을 할 수 있는 깨끗한 방법은 없습니다.

저는 이전에 이 질문을 주제 외로 종결하기로 투표했지만 안타깝게도 아직 지원 라이브러리의 일부가 아닌 시각적 효과가 꽤 좋아서 마음을 바꿨습니다.향후 업데이트 시 나타날 가능성이 높지만 발표된 기간은 없습니다.

다행히 이미 사용 가능한 맞춤형 구현이 거의 없습니다.

이전 버전의 Android와 호환되는 Material 테마 위젯 세트 포함:

그래서 당신은 이것들 중 하나를 시도하거나 구글에서 다른 "소재 위젯"을 시도할 수 있습니다.

리플 버튼을 만드는 간단한 클래스를 만들었습니다. 결국 필요가 없었기 때문에 최고는 아니지만 여기 있습니다.

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

편집

많은 사람들이 이와 같은 것을 찾고 있기 때문에 저는 다른 뷰들이 파급효과를 갖도록 할 수 있는 수업을 만들었습니다.

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

사용자 정의 배경이 있는 경우가 있습니다. 이 경우 더 나은 솔루션을 사용하는 것이 좋습니다.android:foreground="?selectableItemBackground"

갱신하다

둥근 모서리와 사용자 정의 배경으로 리플 효과를 원하는 경우 다음을 사용할 수 있습니다.

background_ ripple.xml

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/ripple_color">
<item android:id="@android:id/mask">
    <shape android:shape="rectangle">
        <solid android:color="@android:color/white" />
        <corners android:radius="5dp" />
    </shape>
</item>

<item android:id="@android:id/background">
    <shape android:shape="rectangle">
        <solid android:color="@android:color/white" />
        <corners android:radius="5dp" />
    </shape>
</item>

layout.xml

<Button
    android:id="@+id/btn_play"
    android:background="@drawable/background_ripple"
    app:backgroundTint="@color/colorPrimary"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/play_now" />

API >= 21에서 사용하였습니다.

아주 간단해요 ;-)

먼저 오래된 api 버전과 최신 api 버전의 api 버전의 drawable 파일을 두 개 만들어야 합니다. 물론이죠! 최신 api 버전 안드로이드 스튜디오의 drawable 파일을 생성하면 오래된 api 버전을 자동으로 생성할 것을 제안하고 마지막으로 이 drawable을 배경 뷰로 설정합니다.

새 api 버전(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>

이전 api 버전(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>

리플 뽑기 가능한 것에 대한 자세한 정보는 여기를 방문하세요: https://developer.android.com/reference/android/graphics/drawable/RippleDrawable.html

때로는 이 선을 모든 레이아웃이나 구성요소에 사용할 수 있습니다.

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

언급URL : https://stackoverflow.com/questions/26604134/how-to-achieve-ripple-animation-using-support-library

반응형