Colorfulbar to customize Ripple effects

Keywords: Android github Attribute xml

An effect recently seen on github. Just recently, I have been learning to customize ViewGroup in advanced level, so I started to implement this effect in a small way.
The original effect: originated from the ____________ open-android/Android1

open-android/Android1 yes open-android/Android A sublink below.

Original effect: There are some differences in the implementation of the effect, don't care about these details.

Think about how to do it:
1. Is it better to use custom View or custom ViewGroup for icons and text?
2. Animation of icons and words
3. Realization of Ripple effect
Answer:
1. Icons and text are better implemented with custom ViewGroup, with an ImageView and TextView in the middle. Why? In order to verify whether it's better to customize View Group or customize View Group, I spent 2-3 nights trying to code it. I came to the conclusion that customized ViewGroup is more concise and easy to animate.
2. Animation is naturally the attribute animation of View.
3. The outermost layer is the rewritten Linear Layout, which is redrawn according to the coordinates clicked to achieve ripple effect.

AnimItem: ViewGroup with icon + Text

public class AnimItemV extends ViewGroup implements View.OnClickListener

Membership variables. Among them, status shows only icon when it is 1, and icon and text when it is 2. The initial value defaults to 1, showing icon.

    private ImageView icon;
    private TextView text;

    private int x;
    private int y;

    //1-icon,2-icon+text
    private int status = 1;

    private static final String TAG = AnimItemV.class.getSimpleName();

    public AnimItemV(Context context)
    {
        this(context, null);
    }

    public AnimItemV(Context context, AttributeSet attrs)
    {
        this(context, attrs, -1);
    }

    public AnimItemV(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        setOnClickListener(this);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public AnimItemV(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
    {
        super(context, attrs, defStyleAttr, defStyleRes);
        setOnClickListener(this);
    }

onMeassure

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        setTag(1);
        int childCount = getChildCount();
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);


        /**
         * wrap_content Lower width, height
         */

        int width = 0;
        int height = 0;


        for (int i = 0; i < childCount; i++)
        {
            View childView = getChildAt(i);
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);

            MarginLayoutParams childMargin = (MarginLayoutParams) childView.getLayoutParams();

            //Width of child view
            int childWidth = childView.getMeasuredWidth() + childMargin.leftMargin + childMargin.rightMargin;
            //The height of child view
            int childHeight = childView.getMeasuredHeight() + childMargin.topMargin + childMargin.bottomMargin;


            width = Math.max(width, childWidth);
            height += childHeight;


        }


        width = Math.max(width, height);
        height = Math.max(width, height);


        //sizeWidth = width, sizeHeight = height is to deal with the weight situation
        sizeWidth = width;
        sizeHeight = height;

        setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth : width + getPaddingLeft() + getPaddingRight(),
                (heightMode == MeasureSpec.EXACTLY) ? sizeHeight : height + getPaddingTop() + getPaddingBottom());


    }

On Layout, the calculation of coordinates is not complicated, you can draw a draft to understand.

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        int width = getWidth();
        int height = getHeight();


        icon = (ImageView) getChildAt(0);
        MarginLayoutParams iconMargin = (MarginLayoutParams) icon.getLayoutParams();
        int iconWidth = icon.getMeasuredWidth() + iconMargin.leftMargin + iconMargin.rightMargin;
        int iconHeight = icon.getMeasuredHeight() + iconMargin.topMargin + iconMargin.bottomMargin;


        text = (TextView) getChildAt(1);
        text.setAlpha(0.0f);
        MarginLayoutParams textMargin = (MarginLayoutParams) text.getLayoutParams();
        int textWidth = text.getMeasuredWidth() + textMargin.leftMargin + textMargin.rightMargin;
        int textHeight = text.getMeasuredHeight() + textMargin.topMargin + textMargin.bottomMargin;


        int lc = (width - iconWidth) / 2;
        int tc = (width - iconWidth) / 2;
        int rc = lc + iconWidth;
        int bc = tc + iconHeight;

        icon.layout(lc, tc, rc, bc);


        int lc1 = (width - textWidth) / 2;
        int tc1 = bc;
        int rc1 = lc1 + textWidth;
        int bc1 = tc1 + textHeight;


        /**
         * -tc All fonts are displayed
         */
//            text.layout(lc1,tc1-tc,rc1,bc1-tc);
        text.layout(lc1, tc1, rc1, bc1);
    }

Finally, the property animation of ImageView and TextView provides Public methods for external calls. When writing recovery animation, special attention should be paid to the numerical changes of Y axis. See the notes.

    /**
     * icon Move up, text up, animation up
     */
    public void startAnim()
    {
        MarginLayoutParams iconMargin = (MarginLayoutParams) icon.getLayoutParams();
        int iconWidth = icon.getMeasuredWidth() + iconMargin.leftMargin + iconMargin.rightMargin;
        int tc = (getWidth() - iconWidth) / 2;

        //icon Y moves tc up axially
        ObjectAnimator iconYAnim = ObjectAnimator.ofFloat(icon, "TranslationY", -tc).setDuration(500);

        //text transparency changes from 0 to 1
        ObjectAnimator textAlphaAnim = ObjectAnimator.ofFloat(text, "Alpha", text.getAlpha(), 1.0f).setDuration(500);
        //Move tc up the text Y axis
        ObjectAnimator textYAnim = ObjectAnimator.ofFloat(text, "TranslationY", -tc).setDuration(500);


        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(iconYAnim, textAlphaAnim, textYAnim);
        animatorSet.start();

        setTag(2);


    }

    /**
     * Recovery animation
     */
    public void reverseAnim()
    {
        MarginLayoutParams iconMargin = (MarginLayoutParams) icon.getLayoutParams();
        int iconWidth = icon.getMeasuredWidth() + iconMargin.leftMargin + iconMargin.rightMargin;
        int tc = (getWidth() - iconWidth) / 2;

        /**
         *Notice that here is 0, because it's - tc+tc, so it's 0.
         */
        //icon Y moves down axially 0
        ObjectAnimator iconYAnim = ObjectAnimator.ofFloat(icon, "TranslationY", 0).setDuration(500);

        //text transparency changes from 1 to 0
        ObjectAnimator textAlphaAnim = ObjectAnimator.ofFloat(text, "Alpha", text.getAlpha(), 0.0f).setDuration(500);
        //Move 0 downward in text Y axis
        ObjectAnimator textYAnim = ObjectAnimator.ofFloat(text, "TranslationY", 0).setDuration(500);


        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(iconYAnim, textAlphaAnim, textYAnim);
        animatorSet.start();

        setTag(1);

    }

Finally, the onckick event is added to determine which animation effect should be performed by the status obtained from tag.

    @Override
    public void onClick(View view)
    {
        if ((int) getTag() == 1)
        {
            startAnim();
        }
        else if ((int) getTag() == 2)
        {
            reverseAnim();
        }
    }

AnimLinearLayout

public class AnimLinearLayout extends LinearLayout
    //Store a set of colors
    private int[] rippleColors;
    //X-axis coordinates of sub-ViewGroup
    private int childX;
    //Y-axis coordinates of sub-ViewGroup
    private int childY;
    //Radius of ripple
    private float radius;
    //The color of ripple
    private int rippleColor;

    //....getter and setter....

    public AnimLinearLayout(Context context, AttributeSet attrs)
    {
        this(context, attrs, -1);
    }

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

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public AnimLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
    {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

In onAttached To Window, traverse sub view s and set OnTouchListener for each subview

    @Override
    protected void onAttachedToWindow()
    {
        super.onAttachedToWindow();


        int childCount = getChildCount();

        for (int i = 0; i < childCount; i++)
        {

            final AnimItemV animItemV = (AnimItemV) getChildAt(i);
            animItemV.setOnTouchListener(new OnTouchListener()
            {
                @Override
                public boolean onTouch(View view, MotionEvent motionEvent)
                {
                    for (int i = 0; i < getChildCount(); i++)
                    {
                        if (animItemV.getId() == getChildAt(i).getId())
                        {
                            continue;
                        }
                        ((AnimItemV) getChildAt(i)).reverseAnim();
                    }
                    return false;
                }
            });
        }

    }

In onInterceptTouchEvent, the coordinates of x and y are obtained when the finger is pressed, and the corresponding animation effect is performed when the finger is loosened.

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event)
    {
        Log.e(TAG, TAG + " onInterceptTouchEvent");
        int action = event.getAction();
        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                int childCount = getChildCount();
                int x = (int) event.getX();
                int y = (int) event.getY();

                Log.e(TAG, "x-->" + x);
                for (int i = 0; i < childCount; i++)
                {
                    AnimItemV animItemV = (AnimItemV) getChildAt(i);
                    if(x>=animItemV.getTop()&&x<=animItemV.getRight())
                    {
                        setRippleColor(getResources().getColor(rippleColors[i]));
                        break;
                    }
                }


                setChildX(x);
                setChildY(y);


                break;

            case MotionEvent.ACTION_UP:
                int radius = Math.max(getWidth(), getHeight());
                ValueAnimator animator = ValueAnimator.ofInt(0, radius);
                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
                {
                    @Override
                    public void onAnimationUpdate(ValueAnimator valueAnimator)
                    {
                        int currentRadius = (int) valueAnimator.getAnimatedValue();
                        setRadius((float) (currentRadius));
                        invalidate();
                    }
                });

                animator.setInterpolator(new AccelerateDecelerateInterpolator());
                animator.start();

                animator.addListener(new AnimatorListenerAdapter()
                {
                    @Override
                    public void onAnimationEnd(Animator animation)
                    {
                        super.onAnimationEnd(animation);
                        setBackgroundColor(rippleColor);
                    }
                });
                break;

        }


        return super.onInterceptTouchEvent(event);
    }

Because layout.xml inherits from LinearLayout, some properties of LinearLayout can be used directly.

<com.gaoyy.learningcustomview.view.AnimLinearLayout
        android:id="@+id/activity_colorful_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#888888"
        android:gravity="center"
        android:orientation="horizontal"
        tools:context="com.gaoyy.learningcustomview.ui.ColorfulBarActivity"
        android:layout_alignParentBottom="true">


        <com.gaoyy.learningcustomview.view.AnimItemV
            android:id="@+id/item1"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            >


            <ImageView
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:src="@mipmap/ic_moment"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Moment"
                android:textColor="#ffffff"
                android:textSize="16sp"/>


        </com.gaoyy.learningcustomview.view.AnimItemV>

        <com.gaoyy.learningcustomview.view.AnimItemV
            android:id="@+id/item2"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            >


            <ImageView
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:src="@mipmap/ic_friend"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Friend"
                android:textColor="#ffffff"
                android:textSize="16sp"/>


        </com.gaoyy.learningcustomview.view.AnimItemV>

        ...


    </com.gaoyy.learningcustomview.view.AnimLinearLayout>

Project address: https://github.com/gaoyuyu/LearningCustomView

Posted by louie on Fri, 12 Apr 2019 01:06:33 -0700