समर्थन पुस्तकालय का उपयोग करके लहर एनीमेशन कैसे प्राप्त करें?


171

मैं बटन क्लिक पर तरंग एनीमेशन जोड़ने की कोशिश कर रहा हूं। मुझे नीचे पसंद आया लेकिन इसमें 21 तक minSdKVersion की आवश्यकता है।

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"

    जावा कोड में xml संदर्भों को हल करने के लिए यहां एक नज़र डालें ?(attr)

लाइब्रेरी का समर्थन करें

  • समर्थन पुस्तकालय के संदर्भ के बजाय ?attr:(या ?आशुलिपि) का उपयोग करना , इसलिए एपीआई 7 पर वापस उपलब्ध है।?android:attr

छवियों / पृष्ठभूमि के साथ तरंग

  • एक छवि या पृष्ठभूमि और ओवरप्ले करने के लिए सबसे आसान समाधान ओवरले करना Viewहोता है FrameLayout, जिसमें रिपल सेट के साथ setForeground()या उसके साथ लपेटना होता है setBackground()

ईमानदारी से अन्यथा ऐसा करने का कोई साफ तरीका नहीं है।


38
यह 21 से पहले के संस्करणों में तरंग समर्थन को नहीं जोड़ता है।
AndroidDev

21
यह लहर समर्थन नहीं जोड़ सकता है लेकिन यह समाधान अच्छी तरह से नीचा दिखाता है। यह वास्तव में उस विशेष मुद्दे को हल करता है जो मैं कर रहा था। मैं एल पर एक लहर प्रभाव और Android के पूर्व संस्करण पर एक सरल चयन चाहता था।
डेव जेन्सेन

4
@AndroidDev, @Dave Jensen: वास्तव में, v7 सपोर्ट लाइब्रेरी ?attr:के ?android:attrसंदर्भों के बजाय , जिसका उपयोग करते हुए, आप इसका उपयोग करते हैं, आपको एपीआई 7. बैकवर्ड संगतता प्रदान करता है। देखें: developer.android.com/tools/support-live/features। html # v7
बेन डी ला हाय

14
क्या होगा अगर मैं भी पृष्ठभूमि रंग रखना चाहता हूं?
स्टेनले सनतोसो

9
Ripple का प्रभाव API <21 के लिए नहीं है। Ripple सामग्री डिज़ाइन का एक क्लिक प्रभाव है। Google डिज़ाइन टीम का परिप्रेक्ष्य इसे प्री-लॉलीपॉप उपकरणों पर नहीं दिखाता है। प्री-लॉलीपॉप के अपने स्वयं के क्लिक प्रभाव होते हैं (हल्के नीले कवर के लिए चूक)। प्रस्तावित उत्तर सिस्टम के डिफ़ॉल्ट क्लिक-प्रभाव का उपयोग करने का सुझाव देता है। यदि आप क्लिक प्रभाव के रंगों को अनुकूलित करना चाहते हैं, तो आपको एक ड्रा करने योग्य बनाने की आवश्यकता होती है और इसे रिपल क्लिक-प्रभाव (<ripple> drawable के साथ) के लिए Res / drawable-v21 पर रख सकते हैं, और गैर के लिए Res / drawable पर तरंग क्लिक प्रभाव (<चयनकर्ता के साथ (आमतौर पर योग्य))
nbtk

55

मैंने पूर्व में इस प्रश्न को ऑफ-टॉपिक के रूप में बंद करने के लिए मतदान किया था, लेकिन वास्तव में मैंने अपना दिमाग बदल दिया क्योंकि यह काफी अच्छा दृश्य प्रभाव है, जो दुर्भाग्य से, अभी तक समर्थन पुस्तकालय का हिस्सा नहीं है। यह सबसे अधिक संभावना है कि भविष्य के अद्यतन में दिखाई देगा, लेकिन कोई समय सीमा की घोषणा नहीं की गई है।

सौभाग्य से कुछ कस्टम कार्यान्वयन पहले से ही उपलब्ध हैं:

मेटरलिअल थीम्ड विजेट सहित, एंड्रॉइड के पुराने संस्करणों के साथ संगत:

इसलिए आप इनमें से किसी एक या "अन्य सामग्री विजेट" के लिए 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;
    }
}

संपादित करें

चूंकि बहुत से लोग इस तरह की चीज की तलाश कर रहे हैं, इसलिए मैंने एक ऐसा वर्ग बनाया जो अन्य विचारों को तरंग प्रभाव डाल सकता है:

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); }
वलोडिमिर कुल्क

लागू करने के लिए सरल ... प्लग एंड प्ले :)
रंजीथ कुमार

यदि मैं एक पुनर्नवीनीकरण दृश्य के प्रत्येक दृश्य पर इस वर्ग का उपयोग करता हूं तो मुझे ClassCastException मिल रही है।
अली_विर्स

1
@Ali_Waris सपोर्ट लाइब्रेरी इन दिनों तरंगों से निपट सकती है लेकिन यह सब ठीक करने के लिए आपको रिपल इफेक्ट addRippleToViewको जोड़ने के बजाय उपयोग करना होगा। बल्कि प्रत्येक दृश्य RecyclerViewRippleViewCreator
निकोलस टायलर

17

कभी-कभी आपके पास एक कस्टम पृष्ठभूमि होती है, उस स्थिति में एक बेहतर समाधान उपयोग होता है android:foreground="?selectableItemBackground"


2
हां, लेकिन यह एपीआई> = 23 पर या 21 एपीआई वाले उपकरणों पर काम करता है, लेकिन केवल कार्ड व्यू या फ्रेमलैयौट में
Skullper

17

यह बहुत सरल है ;-)

पहले आपको पुराने एपीआई संस्करण के लिए दो ड्रा करने योग्य फ़ाइल एक और नवीनतम संस्करण के लिए एक और एक बनाना होगा; यदि आप नवीनतम एपीआई संस्करण के लिए ड्रा करने योग्य फ़ाइल बनाते हैं, तो एंड्रॉइड स्टूडियो आपको स्वचालित रूप से पुराना बनाने का सुझाव देता है। और अंत में इस ड्रॉबल को अपने बैकग्राउंड व्यू में सेट करें।

नए एपीआई संस्करण (रेस / ड्रॉबल-वी 21 / रिप्पल.एक्सएमएल) के लिए नमूना

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

पुराने एपीआई संस्करण के लिए नमूना योग्य (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


1
यह वास्तव में बहुत सरल है!
आदित्य एस।

यह समाधान निश्चित रूप से बहुत अधिक उत्थान होना चाहिए! धन्यवाद।
जेरेबकजुब

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 Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.