Android Floating Action Button For Material Design (Android L).
Create New Project In Android Eclips.
—> AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.buttonformaterialdesign" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="22" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
—>Now Create xml folder in res.
fab_icon_states.xml
<?xml version="1.0" encoding="utf-8"?> <state-list> <state> <points> <item>0.5</item> <item>0</item> <item>0.5</item> <item>1</item> <item>0</item> <item>0.5</item> <item>1</item> <item>0.5</item> </points> </state> <state> <points> <item>0.633</item> <item>0.161</item> <item>0</item> <item>0.793</item> <item>0.633</item> <item>0.161</item> <item>1</item> <item>0.527</item> </points> <links> <item>0</item> <item>1</item> </links> </state> </state-list>
attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="RippleView"> <attr name="rd_style" /> <attr name="rd_enable" /> </declare-styleable> <attr name="rd_style" format="reference"/> <attr name="rd_enable" format="boolean"/> <declare-styleable name="FloatingActionButton"> <attr name="fab_backgroundColor" format="reference|color"/> <attr name="fab_radius" format="reference|dimension"/> <attr name="fab_elevation" format="reference|dimension"/> <attr name="fab_iconSrc" format="reference"/> <attr name="fab_iconLineMorphing" format="reference"/> <attr name="fab_iconSize" format="reference|dimension"/> <attr name="fab_interpolator" format="reference"/> <attr name="fab_animDuration" format="reference|integer"/> </declare-styleable> <declare-styleable name="RippleDrawable"> <attr name="rd_backgroundColor" format="reference|color"/> <attr name="rd_backgroundAnimDuration" format="reference|integer"/> <attr name="rd_maxRippleRadius" format="reference|dimension|integer"> <enum name="match_view" value="0x00000000" /> </attr> <attr name="rd_rippleColor" format="reference|color"/> <attr name="rd_rippleAnimDuration" format="reference|integer"/> <attr name="rd_inInterpolator" format="reference"/> <attr name="rd_outInterpolator" format="reference"/> <attr name="rd_maskType" format="integer"> <enum name="rectangle" value="0x00000000" /> <enum name="oval" value="0x00000001" /> </attr> <attr name="rd_rippleType" format="integer"> <enum name="touch" value="0x00000000" /> <enum name="wave" value="0x00000001" /> </attr> <attr name="rd_cornerRadius" format="reference|dimension"/> <attr name="rd_topLeftCornerRadius" format="reference|dimension"/> <attr name="rd_topRightCornerRadius" format="reference|dimension"/> <attr name="rd_bottomLeftCornerRadius" format="reference|dimension"/> <attr name="rd_bottomRightCornerRadius" format="reference|dimension"/> <attr name="rd_padding" format="reference|dimension"/> <attr name="rd_leftPadding" format="reference|dimension"/> <attr name="rd_topPadding" format="reference|dimension"/> <attr name="rd_rightPadding" format="reference|dimension"/> <attr name="rd_bottomPadding" format="reference|dimension"/> <attr name="rd_delayClick" format="boolean"/> </declare-styleable> <declare-styleable name="LineMorphingDrawable"> <attr name="lmd_state" format="reference"/> <attr name="lmd_curState" format="integer"/> <attr name="lmd_padding" format="reference|dimension"/> <attr name="lmd_paddingLeft" format="reference|dimension"/> <attr name="lmd_paddingTop" format="reference|dimension"/> <attr name="lmd_paddingRight" format="reference|dimension"/> <attr name="lmd_paddingBottom" format="reference|dimension"/> <attr name="lmd_animDuration" format="reference|integer"/> <attr name="lmd_interpolator" format="reference"/> <attr name="lmd_strokeSize" format="reference|dimension"/> <attr name="lmd_strokeColor" format="reference|color"/> <attr name="lmd_strokeCap" format="integer"> <enum name="butt" value="0x00000000" /> <enum name="round" value="0x00000001" /> <enum name="square" value="0x00000002" /> </attr> <attr name="lmd_strokeJoin" format="integer"> <enum name="miter" value="0x00000000" /> <enum name="round" value="0x00000001" /> <enum name="bevel" value="0x00000002" /> </attr> <attr name="lmd_clockwise" format="boolean"/> </declare-styleable> <declare-styleable name="RadioButtonDrawable"> <attr name="rbd_width" format="reference|dimension"/> <attr name="rbd_height" format="reference|dimension"/> <attr name="rbd_strokeSize" format="reference|dimension"/> <attr name="rbd_radius" format="reference|dimension"/> <attr name="rbd_innerRadius" format="reference|dimension"/> <attr name="rbd_strokeColor" format="reference|color"/> <attr name="rbd_animDuration" format="reference|integer"/> </declare-styleable> </resources>
dimens.xml
<resources> <!-- Default screen margins, per the Android Design guidelines. --> <dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen> <dimen name="title_text">16sp</dimen> <dimen name="bt_height">36dp</dimen> </resources>
styles.xml
<resources> <!-- Base application theme, dependent on API level. This theme is replaced by AppBaseTheme from res/values-vXX/styles.xml on newer devices. --> <style name="AppBaseTheme" parent="Theme.AppCompat.Light"> <!-- Theme customizations available in newer API levels can go in res/values-vXX/styles.xml, while customizations related to backward-compatibility can go here. --> </style> <!-- Application theme. --> <style name="AppTheme" parent="AppBaseTheme"> <!-- All customizations that are NOT specific to a particular API-level can go here. --> </style> <style name="FlatButtonRippleStyle" parent="Material.Drawable.Ripple.Touch.Light"> <item name="android:background">@null</item> <item name="rd_cornerRadius">4dp</item> </style> <style name="FlatWaveButtonRippleStyle" parent="Material.Drawable.Ripple.Wave.Light"> <item name="android:background">@null</item> <item name="rd_cornerRadius">4dp</item> </style> <style name="FlatColorButtonRippleStyle" parent="Material.Drawable.Ripple.Touch.Light"> <item name="android:background">@null</item> <item name="rd_backgroundColor">#33219AFF</item> <item name="rd_rippleColor">#33219AFF</item> <item name="rd_cornerRadius">4dp</item> </style> <style name="FlatWaveColorButtonRippleStyle" parent="Material.Drawable.Ripple.Wave.Light"> <item name="android:background">@null</item> <item name="rd_rippleColor">#902DA0FF</item> <item name="rd_cornerRadius">4dp</item> </style> <style name="RaiseButtonRippleStyle" parent="Material.Drawable.Ripple.Touch.Light"> <item name="android:background">@drawable/bg_bt_raise</item> <item name="rd_leftPadding">1dp</item> <item name="rd_topPadding">1dp</item> <item name="rd_rightPadding">1dp</item> <item name="rd_bottomPadding">2dp</item> </style> <style name="RaiseWaveButtonRippleStyle" parent="Material.Drawable.Ripple.Wave.Light"> <item name="android:background">@drawable/bg_bt_raise</item> <item name="rd_leftPadding">1dp</item> <item name="rd_topPadding">1dp</item> <item name="rd_rightPadding">1dp</item> <item name="rd_bottomPadding">2dp</item> </style> <style name="RaiseColorButtonRippleStyle" parent="Material.Drawable.Ripple.Touch.Light"> <item name="android:background">@drawable/bg_bt_raise_color</item> <item name="rd_rippleColor">#33000000</item> <item name="rd_leftPadding">1dp</item> <item name="rd_topPadding">1dp</item> <item name="rd_rightPadding">1dp</item> <item name="rd_bottomPadding">2dp</item> </style> <style name="RaiseWaveColorButtonRippleStyle" parent="Material.Drawable.Ripple.Wave.Light"> <item name="android:background">@drawable/bg_bt_raise_color</item> <item name="rd_rippleColor">#33000000</item> <item name="rd_leftPadding">1dp</item> <item name="rd_topPadding">1dp</item> <item name="rd_rightPadding">1dp</item> <item name="rd_bottomPadding">2dp</item> </style> <style name="FloatingActionButton" parent="Material.Drawable.Ripple.Touch.Light"> <item name="fab_backgroundColor">#FFDFDFDF</item> <item name="fab_elevation">4dp</item> <item name="fab_iconLineMorphing">@style/FloatingActionButtonIcon.Light</item> <item name="rd_delayClick">false</item> </style> <style name="FloatingWaveActionButton" parent="Material.Drawable.Ripple.Wave.Light"> <item name="fab_backgroundColor">#FFDFDFDF</item> <item name="fab_elevation">4dp</item> <item name="fab_iconLineMorphing">@style/FloatingActionButtonIcon.Light</item> <item name="rd_delayClick">false</item> </style> <style name="FloatingColorActionButton" parent="Material.Drawable.Ripple.Touch.Light"> <item name="rd_rippleColor">#33000000</item> <item name="fab_backgroundColor">#FF2196F3</item> <item name="fab_elevation">4dp</item> <item name="fab_iconLineMorphing">@style/FloatingActionButtonIcon</item> <item name="rd_delayClick">true</item> </style> <style name="FloatingWaveColorActionButton" parent="Material.Drawable.Ripple.Wave.Light"> <item name="rd_rippleColor">#33000000</item> <item name="fab_backgroundColor">#FF2196F3</item> <item name="fab_elevation">4dp</item> <item name="fab_iconLineMorphing">@style/FloatingActionButtonIcon</item> <item name="rd_delayClick">true</item> </style> <style name="FloatingActionButtonIcon" > <item name="lmd_state">@xml/fab_icon_states</item> <item name="lmd_curState">0</item> <item name="lmd_padding">2dp</item> <item name="lmd_animDuration">400</item> <item name="lmd_interpolator">@android:anim/accelerate_decelerate_interpolator</item> <item name="lmd_strokeSize">3dp</item> <item name="lmd_strokeColor">#FFFFFFFF</item> <item name="lmd_strokeCap">butt</item> <item name="lmd_strokeJoin">miter</item> <item name="lmd_clockwise">true</item> </style> <style name="FloatingActionButtonIcon.Light" parent="FloatingActionButtonIcon"> <item name="lmd_strokeColor">#FF000000</item> </style> <style name="Material.Widget.FloatingActionButton" parent="Material.Drawable.Ripple.Touch"> <item name="fab_radius">28dp</item> <item name="fab_elevation">4dp</item> <item name="fab_iconSize">24dp</item> <item name="fab_interpolator">@android:anim/decelerate_interpolator</item> <item name="fab_animDuration">@android:integer/config_mediumAnimTime</item> </style> <style name="Material.Widget.FloatingActionButton.Light" parent="Material.Drawable.Ripple.Touch.Light"> <item name="fab_radius">28dp</item> <item name="fab_elevation">4dp</item> <item name="fab_iconSize">24dp</item> <item name="fab_interpolator">@android:anim/decelerate_interpolator</item> <item name="fab_animDuration">@android:integer/config_mediumAnimTime</item> </style> <style name="Material"></style> <style name="Material.Drawable"></style> <style name="Material.Widget"></style> <style name="Material.App"></style> <style name="Material.TextAppearance"></style> <style name="Material.Drawable.Ripple"> <item name="rd_enable">true</item> <item name="rd_inInterpolator">@android:anim/decelerate_interpolator</item> <item name="rd_outInterpolator">@android:anim/decelerate_interpolator</item> <item name="rd_maskType">rectangle</item> <item name="rd_cornerRadius">2dp</item> <item name="rd_padding">0dp</item> <item name="rd_delayClick">false</item> </style> <style name="Material.Drawable.Ripple.Touch" parent="Material.Drawable.Ripple"> <item name="rd_backgroundColor">#26CCCCCC</item> <item name="rd_backgroundAnimDuration">200</item> <item name="rd_maxRippleRadius">48dp</item> <item name="rd_rippleColor">#19CCCCCC</item> <item name="rd_rippleAnimDuration">300</item> <item name="rd_rippleType">touch</item> </style> <style name="Material.Drawable.Ripple.Touch.MatchView" parent="Material.Drawable.Ripple.Touch"> <item name="rd_maxRippleRadius">match_view</item> </style> <style name="Material.Drawable.Ripple.Touch.Light" parent="Material.Drawable.Ripple.Touch"> <item name="rd_backgroundColor">#33999999</item> <item name="rd_rippleColor">#33999999</item> </style> <style name="Material.Drawable.Ripple.Touch.MatchView.Light" parent="Material.Drawable.Ripple.Touch"> <item name="rd_maxRippleRadius">match_view</item> <item name="rd_backgroundColor">#33999999</item> <item name="rd_rippleColor">#33999999</item> </style> <style name="Material.Drawable.Ripple.Wave" parent="Material.Drawable.Ripple"> <item name="rd_maxRippleRadius">48dp</item> <item name="rd_rippleColor">#3FCCCCCC</item> <item name="rd_rippleAnimDuration">200</item> <item name="rd_rippleType">wave</item> </style> <style name="Material.Drawable.Ripple.Wave.Light" parent="Material.Drawable.Ripple.Wave"> <item name="rd_rippleColor">#66999999</item> </style> </resources>
activity_main.xml.
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" android:scrollbarStyle="outsideOverlay"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:gravity="center" android:padding="8dp"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="@dimen/title_text" android:padding="8dp" android:textColor="#FF000000" android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" android:text="Flat"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:weightSum="1.0" android:gravity="center"> <com.rey.material.widget.Button style="@style/FlatButtonRippleStyle" android:id="@+id/button_bt_flat" android:layout_width="0dp" android:layout_height="@dimen/bt_height" android:layout_weight="0.4" android:textColor="#FF000000" android:textAppearance="@style/Base.TextAppearance.AppCompat.Button" android:text="BUTTON"/> <View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="0.1"/> <com.rey.material.widget.Button style="@style/FlatColorButtonRippleStyle" android:id="@+id/button_bt_flat_color" android:layout_width="0dp" android:layout_height="@dimen/bt_height" android:layout_weight="0.4" android:textColor="#FF2196F3" android:textAppearance="@style/Base.TextAppearance.AppCompat.Button" android:text="BUTTON" app:rd_delayClick="true"/> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:weightSum="1.0" android:gravity="center" android:paddingTop="8dp"> <com.rey.material.widget.Button style="@style/FlatWaveButtonRippleStyle" android:id="@+id/button_bt_flat_wave" android:layout_width="0dp" android:layout_height="@dimen/bt_height" android:layout_weight="0.4" android:textColor="#FF000000" android:textAppearance="@style/Base.TextAppearance.AppCompat.Button" android:text="BUTTON"/> <View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="0.1"/> <com.rey.material.widget.Button style="@style/FlatWaveColorButtonRippleStyle" android:id="@+id/button_bt_flat_wave_color" android:layout_width="0dp" android:layout_height="@dimen/bt_height" android:layout_weight="0.4" android:textColor="#FF2196F3" android:textAppearance="@style/Base.TextAppearance.AppCompat.Button" android:text="BUTTON" app:rd_delayClick="true"/> </LinearLayout> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="@dimen/title_text" android:padding="8dp" android:textColor="#FF000000" android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" android:text="Raise"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:weightSum="1.0" android:gravity="center"> <com.rey.material.widget.Button style="@style/RaiseButtonRippleStyle" android:id="@+id/button_bt_raise" android:layout_width="0dp" android:layout_height="@dimen/bt_height" android:layout_weight="0.4" android:textColor="#FF000000" android:textAppearance="@style/Base.TextAppearance.AppCompat.Button" android:text="BUTTON"/> <View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="0.1"/> <com.rey.material.widget.Button style="@style/RaiseColorButtonRippleStyle" android:id="@+id/button_bt_raise_color" android:layout_width="0dp" android:layout_height="@dimen/bt_height" android:layout_weight="0.4" android:textColor="#FFFFFFFF" android:textAppearance="@style/Base.TextAppearance.AppCompat.Button" android:text="BUTTON" app:rd_delayClick="true"/> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:weightSum="1.0" android:gravity="center" android:paddingTop="8dp"> <com.rey.material.widget.Button style="@style/RaiseWaveButtonRippleStyle" android:id="@+id/button_bt_raise_wave" android:layout_width="0dp" android:layout_height="@dimen/bt_height" android:layout_weight="0.4" android:textColor="#FF000000" android:textAppearance="@style/Base.TextAppearance.AppCompat.Button" android:text="BUTTON"/> <View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="0.1"/> <com.rey.material.widget.Button style="@style/RaiseWaveColorButtonRippleStyle" android:id="@+id/button_bt_raise_wave_color" android:layout_width="0dp" android:layout_height="@dimen/bt_height" android:layout_weight="0.4" android:textColor="#FFFFFFFF" android:textAppearance="@style/Base.TextAppearance.AppCompat.Button" android:text="BUTTON" app:rd_delayClick="true"/> </LinearLayout> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="@dimen/title_text" android:padding="8dp" android:textColor="#FF000000" android:textAppearance="@style/Base.TextAppearance.AppCompat.Title" android:text="Float"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:weightSum="1.0" android:gravity="center"> <com.rey.material.widget.FloatingActionButton style="@style/FloatingActionButton" android:id="@+id/button_bt_float" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <View android:layout_width="100dp" android:layout_height="1dp"/> <com.rey.material.widget.FloatingActionButton style="@style/FloatingColorActionButton" android:id="@+id/button_bt_float_color" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:weightSum="1.0" android:gravity="center" android:padding="8dp"> <com.rey.material.widget.FloatingActionButton style="@style/FloatingWaveActionButton" android:id="@+id/button_bt_float_wave" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <View android:layout_width="100dp" android:layout_height="1dp"/> <com.rey.material.widget.FloatingActionButton style="@style/FloatingWaveColorActionButton" android:id="@+id/button_bt_float_wave_color" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> </LinearLayout> </ScrollView>
MainActivity.java
package com.example.buttonformaterialdesign; import android.app.Activity; import android.os.Bundle; import android.view.View; import com.rey.material.widget.Button; import com.rey.material.widget.FloatingActionButton; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button bt_flat = (Button)findViewById(R.id.button_bt_flat); Button bt_flat_color = (Button)findViewById(R.id.button_bt_flat_color); Button bt_flat_wave = (Button)findViewById(R.id.button_bt_flat_wave); Button bt_flat_wave_color = (Button)findViewById(R.id.button_bt_flat_wave_color); Button bt_raise = (Button)findViewById(R.id.button_bt_raise); Button bt_raise_color = (Button)findViewById(R.id.button_bt_raise_color); Button bt_raise_wave = (Button)findViewById(R.id.button_bt_raise_wave); Button bt_raise_wave_color = (Button)findViewById(R.id.button_bt_raise_wave_color); FloatingActionButton bt_float = (FloatingActionButton)findViewById(R.id.button_bt_float); FloatingActionButton bt_float_color = (FloatingActionButton)findViewById(R.id.button_bt_float_color); FloatingActionButton bt_float_wave = (FloatingActionButton)findViewById(R.id.button_bt_float_wave); FloatingActionButton bt_float_wave_color = (FloatingActionButton)findViewById(R.id.button_bt_float_wave_color); View.OnClickListener listener = new View.OnClickListener() { @Override public void onClick(View v) { if(v instanceof FloatingActionButton){ FloatingActionButton bt = (FloatingActionButton)v; bt.setLineMorphingState((bt.getLineMorphingState() + 1) % 2, true); } } }; View.OnClickListener listener_delay = new View.OnClickListener() { @Override public void onClick(View v) { if(v instanceof FloatingActionButton){ FloatingActionButton bt = (FloatingActionButton)v; bt.setLineMorphingState((bt.getLineMorphingState() + 1) % 2, true); } } }; bt_flat.setOnClickListener(listener); bt_flat_wave.setOnClickListener(listener); bt_raise.setOnClickListener(listener); bt_raise_wave.setOnClickListener(listener); bt_float.setOnClickListener(listener); bt_float_wave.setOnClickListener(listener); bt_flat_color.setOnClickListener(listener_delay); bt_flat_wave_color.setOnClickListener(listener_delay); bt_raise_color.setOnClickListener(listener_delay); bt_raise_wave_color.setOnClickListener(listener_delay); bt_float_color.setOnClickListener(listener_delay); bt_float_wave_color.setOnClickListener(listener_delay); } }
LineMorphingDrawable.java
package com.rey.material.drawable; import java.util.ArrayList; import java.util.List; import org.xmlpull.v1.XmlPullParser; import android.content.Context; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.os.SystemClock; import android.util.AttributeSet; import android.view.animation.AccelerateInterpolator; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import com.example.buttonformaterialdesign.R; import com.rey.material.util.ThemeUtil; import com.rey.material.util.ViewUtil; public class LineMorphingDrawable extends Drawable implements Animatable{ private boolean mRunning = false; private Paint mPaint; private int mPaddingLeft = 12; private int mPaddingTop = 12; private int mPaddingRight = 12; private int mPaddingBottom = 12; private RectF mDrawBound; private int mPrevState; private int mCurState; private long mStartTime; private float mAnimProgress; private int mAnimDuration; private Interpolator mInterpolator; private int mStrokeSize; private int mStrokeColor; private boolean mClockwise; private Paint.Cap mStrokeCap; private Paint.Join mStrokeJoin; private Path mPath; private State[] mStates; private LineMorphingDrawable(State[] states, int curState, int paddingLeft, int paddingTop, int paddingRight, int paddingBottom, int animDuration, Interpolator interpolator, int strokeSize, int strokeColor, Paint.Cap strokeCap, Paint.Join strokeJoin, boolean clockwise){ mStates = states; mPaddingLeft = paddingLeft; mPaddingTop = paddingTop; mPaddingRight = paddingRight; mPaddingBottom = paddingBottom; mAnimDuration = animDuration; mInterpolator = interpolator; mStrokeSize = strokeSize; mStrokeColor = strokeColor; mStrokeCap = strokeCap; mStrokeJoin = strokeJoin; mClockwise = clockwise; mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeCap(mStrokeCap); mPaint.setStrokeJoin(mStrokeJoin); mPaint.setColor(mStrokeColor); mPaint.setStrokeWidth(mStrokeSize); mDrawBound = new RectF(); mPath = new Path(); switchLineState(curState, false); } @Override public void draw(Canvas canvas) { int restoreCount = canvas.save(); float degrees = (mClockwise ? 180 : -180) * ((mPrevState < mCurState ? 0f : 1f) + mAnimProgress); canvas.rotate(degrees, mDrawBound.centerX(), mDrawBound.centerY()); canvas.drawPath(mPath, mPaint); canvas.restoreToCount(restoreCount); } @Override public void setAlpha(int alpha) { mPaint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter cf) { mPaint.setColorFilter(cf); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); mDrawBound.left = bounds.left + mPaddingLeft; mDrawBound.top = bounds.top + mPaddingTop; mDrawBound.right = bounds.right - mPaddingRight; mDrawBound.bottom = bounds.bottom - mPaddingBottom; updatePath(); } public void switchLineState(int state, boolean animation){ if(mCurState != state){ mPrevState = mCurState; mCurState = state; if(animation) start(); else{ mAnimProgress = 1f; updatePath(); } } else if(!animation){ mAnimProgress = 1f; updatePath(); } } public boolean setLineState(int state, float progress){ if(mCurState != state){ mPrevState = mCurState; mCurState = state; mAnimProgress = progress; updatePath(); return true; } else if(mAnimProgress != progress){ mAnimProgress = progress; updatePath(); return true; } return false; } public int getLineState(){ return mCurState; } public int getLineStateCount(){ return mStates == null ? 0 : mStates.length; } public float getAnimProgress(){ return mAnimProgress; } private void updatePath(){ mPath.reset(); if(mStates == null) return; if(mAnimProgress == 0f || (mStates[mPrevState].links != null && mAnimProgress < 0.05f)) updatePathWithState(mPath, mStates[mPrevState]); else if(mAnimProgress == 1f || (mStates[mCurState].links != null && mAnimProgress >0.95f)) updatePathWithState(mPath, mStates[mCurState]); else updatePathBetweenStates(mPath, mStates[mPrevState], mStates[mCurState], mInterpolator.getInterpolation(mAnimProgress)); invalidateSelf(); } private void updatePathWithState(Path path, State state){ if(state.links != null){ for(int i = 0; i < state.links.length; i+= 2){ int index1 = state.links[i] * 4; int index2 = state.links[i + 1] * 4; float x1 = getX(state.points[index1]); float y1 = getY(state.points[index1 + 1]); float x2 = getX(state.points[index1 + 2]); float y2 = getY(state.points[index1 + 3]); float x3 = getX(state.points[index2]); float y3 = getY(state.points[index2 + 1]); float x4 = getX(state.points[index2 + 2]); float y4 = getY(state.points[index2 + 3]); if(x1 == x3 && y1 == y3){ path.moveTo(x2, y2); path.lineTo(x1, y1); path.lineTo(x4, y4); } else if(x1 == x4 && y1 == y4){ path.moveTo(x2, y2); path.lineTo(x1, y1); path.lineTo(x3, y3); } else if(x2 == x3 && y2 == y3){ path.moveTo(x1, y1); path.lineTo(x2, y2); path.lineTo(x4, y4); } else{ path.moveTo(x1, y1); path.lineTo(x2, y2); path.lineTo(x3, y3); } } for(int i = 0, count = state.points.length / 4; i < count; i ++){ boolean exist = false; for(int j = 0; j < state.links.length; j++) if(state.links[j] == i){ exist = true; break; } if(exist) continue; int index = i * 4; path.moveTo(getX(state.points[index]), getY(state.points[index + 1])); path.lineTo(getX(state.points[index + 2]), getY(state.points[index + 3])); } } else{ for(int i = 0, count = state.points.length / 4; i < count; i ++){ int index = i * 4; path.moveTo(getX(state.points[index]), getY(state.points[index + 1])); path.lineTo(getX(state.points[index + 2]), getY(state.points[index + 3])); } } } private void updatePathBetweenStates(Path path, State prev, State cur, float progress){ int count = Math.max(prev.points.length, cur.points.length) / 4; for(int i = 0; i < count; i++){ int index = i * 4; float x1; float y1; float x2; float y2; if(index >= prev.points.length){ x1 = 0.5f; y1 = 0.5f; x2 = 0.5f; y2 = 0.5f; } else{ x1 = prev.points[index]; y1 = prev.points[index + 1]; x2 = prev.points[index + 2]; y2 = prev.points[index + 3]; } float x3; float y3; float x4; float y4; if(index >= cur.points.length){ x3 = 0.5f; y3 = 0.5f; x4 = 0.5f; y4 = 0.5f; } else{ x3 = cur.points[index]; y3 = cur.points[index + 1]; x4 = cur.points[index + 2]; y4 = cur.points[index + 3]; } mPath.moveTo(getX(x1 + (x3 - x1) * progress), getY(y1 + (y3 - y1) * progress)); mPath.lineTo(getX(x2 + (x4 - x2) * progress), getY(y2 + (y4 - y2) * progress)); } } private float getX(float value){ return mDrawBound.left + mDrawBound.width() * value; } private float getY(float value){ return mDrawBound.top + mDrawBound.height() * value; } //Animation: based on http://cyrilmottier.com/2012/11/27/actionbar-on-the-move/ private void resetAnimation(){ mStartTime = SystemClock.uptimeMillis(); mAnimProgress = 0f; } @Override public void start() { resetAnimation(); scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); invalidateSelf(); } @Override public void stop() { if(!isRunning()) return; mRunning = false; unscheduleSelf(mUpdater); invalidateSelf(); } @Override public boolean isRunning() { return mRunning; } @Override public void scheduleSelf(Runnable what, long when) { mRunning = true; super.scheduleSelf(what, when); } private final Runnable mUpdater = new Runnable() { @Override public void run() { update(); } }; private void update(){ long curTime = SystemClock.uptimeMillis(); float value = Math.min(1f, (float)(curTime - mStartTime) / mAnimDuration); if(value == 1f){ setLineState(mCurState, 1f); mRunning = false; } else setLineState(mCurState, mInterpolator.getInterpolation(value)); if(isRunning()) scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); } public static class State{ float[] points; int[] links; public State(){} public State(float[] points, int[] links){ this.points = points; this.links = links; } } public static class Builder{ private int mCurState; private int mPaddingLeft; private int mPaddingTop; private int mPaddingRight; private int mPaddingBottom; private int mAnimDuration; private Interpolator mInterpolator; private int mStrokeSize; private int mStrokeColor; private boolean mClockwise; private Paint.Cap mStrokeCap; private Paint.Join mStrokeJoin; private State[] mStates; private static final String TAG_STATE_LIST = "state-list"; private static final String TAG_STATE = "state"; private static final String TAG_POINTS = "points"; private static final String TAG_LINKS = "links"; private static final String TAG_ITEM = "item"; public Builder(){} public Builder(Context context, int defStyleRes){ this(context, null, 0, defStyleRes); } public Builder(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LineMorphingDrawable, defStyleAttr, defStyleRes); int resId; if((resId = a.getResourceId(R.styleable.LineMorphingDrawable_lmd_state, 0)) != 0) states(readStates(context, resId)); curState(a.getInteger(R.styleable.LineMorphingDrawable_lmd_curState, 0)); padding(a.getDimensionPixelSize(R.styleable.LineMorphingDrawable_lmd_padding, 0)); paddingLeft(a.getDimensionPixelSize(R.styleable.LineMorphingDrawable_lmd_paddingLeft, mPaddingLeft)); paddingTop(a.getDimensionPixelSize(R.styleable.LineMorphingDrawable_lmd_paddingTop, mPaddingTop)); paddingRight(a.getDimensionPixelSize(R.styleable.LineMorphingDrawable_lmd_paddingRight, mPaddingRight)); paddingBottom(a.getDimensionPixelSize(R.styleable.LineMorphingDrawable_lmd_paddingBottom, mPaddingBottom)); animDuration(a.getInteger(R.styleable.LineMorphingDrawable_lmd_animDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime))); if((resId = a.getResourceId(R.styleable.LineMorphingDrawable_lmd_interpolator, 0)) != 0) interpolator(AnimationUtils.loadInterpolator(context, resId)); strokeSize(a.getDimensionPixelSize(R.styleable.LineMorphingDrawable_lmd_strokeSize, ThemeUtil.dpToPx(context, 3))); strokeColor(a.getColor(R.styleable.LineMorphingDrawable_lmd_strokeColor, 0xFFFFFFFF)); int cap = a.getInteger(R.styleable.LineMorphingDrawable_lmd_strokeCap, 0); if(cap == 0) strokeCap(Paint.Cap.BUTT); else if(cap == 1) strokeCap(Paint.Cap.ROUND); else strokeCap(Paint.Cap.SQUARE); int join = a.getInteger(R.styleable.LineMorphingDrawable_lmd_strokeJoin, 0); if(join == 0) strokeJoin(Paint.Join.MITER); else if(join == 1) strokeJoin(Paint.Join.ROUND); else strokeJoin(Paint.Join.BEVEL); clockwise(a.getBoolean(R.styleable.LineMorphingDrawable_lmd_clockwise, true)); a.recycle(); } private State[] readStates(Context context, int id){ XmlResourceParser parser = null; List<State> states = new ArrayList<>(); try { parser = context.getResources().getXml(id); int eventType = parser.getEventType(); String tagName; boolean lookingForEndOfUnknownTag = false; String unknownTagName = null; // This loop will skip to the state-list start tag do { if (eventType == XmlPullParser.START_TAG) { tagName = parser.getName(); if (tagName.equals(TAG_STATE_LIST)) { eventType = parser.next(); break; } throw new RuntimeException("Expecting menu, got " + tagName); } eventType = parser.next(); } while (eventType != XmlPullParser.END_DOCUMENT); boolean reachedEndOfStateList = false; State state = null; List<String> array = new ArrayList<>(); StringBuilder currentValue = new StringBuilder(); while (!reachedEndOfStateList) { switch (eventType) { case XmlPullParser.START_TAG: if (lookingForEndOfUnknownTag) break; tagName = parser.getName(); switch (tagName) { case TAG_STATE: state = new State(); break; case TAG_POINTS: case TAG_LINKS: array.clear(); break; case TAG_ITEM: currentValue.delete(0, currentValue.length()); break; default: lookingForEndOfUnknownTag = true; unknownTagName = tagName; break; } break; case XmlPullParser.END_TAG: tagName = parser.getName(); if (lookingForEndOfUnknownTag && tagName.equals(unknownTagName)) { lookingForEndOfUnknownTag = false; unknownTagName = null; } switch (tagName) { case TAG_STATE_LIST: reachedEndOfStateList = true; break; case TAG_STATE: states.add(state); break; case TAG_POINTS: state.points = new float[array.size()]; for (int i = 0; i < state.points.length; i++) state.points[i] = Float.parseFloat(array.get(i)); break; case TAG_LINKS: state.links = new int[array.size()]; for (int i = 0; i < state.links.length; i++) state.links[i] = Integer.parseInt(array.get(i)); break; case TAG_ITEM: array.add(currentValue.toString()); break; } break; case XmlPullParser.TEXT: currentValue.append(parser.getText()); break; case XmlPullParser.END_DOCUMENT: reachedEndOfStateList = true; break; } eventType = parser.next(); } } catch (Exception e) {} finally { if(parser != null) parser.close(); } if(states.isEmpty()) return null; return states.toArray(new State[states.size()]); } public LineMorphingDrawable build(){ if(mStrokeCap == null) mStrokeCap = Paint.Cap.BUTT; if(mStrokeJoin == null) mStrokeJoin = Paint.Join.MITER; if(mInterpolator == null) mInterpolator = new AccelerateInterpolator(); return new LineMorphingDrawable(mStates, mCurState, mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom, mAnimDuration, mInterpolator, mStrokeSize, mStrokeColor, mStrokeCap, mStrokeJoin, mClockwise); } public Builder states(State... states){ mStates = states; return this; } public Builder curState(int state){ mCurState = state; return this; } public Builder padding(int padding){ mPaddingLeft = padding; mPaddingTop = padding; mPaddingRight = padding; mPaddingBottom = padding; return this; } public Builder paddingLeft(int padding){ mPaddingLeft = padding; return this; } public Builder paddingTop(int padding){ mPaddingTop = padding; return this; } public Builder paddingRight(int padding){ mPaddingRight = padding; return this; } public Builder paddingBottom(int padding){ mPaddingBottom = padding; return this; } public Builder animDuration(int duration){ mAnimDuration = duration; return this; } public Builder interpolator(Interpolator interpolator){ mInterpolator = interpolator; return this; } public Builder strokeSize(int size){ mStrokeSize = size; return this; } public Builder strokeColor(int strokeColor){ mStrokeColor = strokeColor; return this; } public Builder strokeCap(Paint.Cap cap){ mStrokeCap = cap; return this; } public Builder strokeJoin(Paint.Join join){ mStrokeJoin = join; return this; } public Builder clockwise(boolean clockwise){ mClockwise = clockwise; return this; } } }
RippleDrawable.java
package com.rey.material.drawable; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Path.Direction; import android.graphics.PixelFormat; import android.graphics.PointF; import android.graphics.RadialGradient; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.os.SystemClock; import android.util.AttributeSet; import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.animation.AccelerateInterpolator; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import com.example.buttonformaterialdesign.R; import com.rey.material.util.ColorUtil; import com.rey.material.util.ThemeUtil; import com.rey.material.util.ViewUtil; public class RippleDrawable extends Drawable implements Animatable, OnTouchListener { private boolean mRunning = false; private Paint mShaderPaint; private Paint mFillPaint; private Mask mMask; private RadialGradient mInShader; private RadialGradient mOutShader; private Matrix mMatrix; private int mAlpha = 255; private Drawable mBackgroundDrawable; private RectF mBackgroundBounds; private Path mBackground; private int mBackgroundAnimDuration; private int mBackgroundColor; private float mBackgroundAlphaPercent; private PointF mRipplePoint; private float mRippleRadius; private int mRippleType; private int mMaxRippleRadius; private int mRippleAnimDuration; private int mRippleColor; private float mRippleAlphaPercent; private boolean mDelayClick; private Interpolator mInInterpolator; private Interpolator mOutInterpolator; private long mStartTime; private int mState = STATE_OUT; private static final int STATE_OUT = 0; private static final int STATE_PRESS = 1; private static final int STATE_HOVER = 2; private static final int STATE_RELEASE_ON_HOLD = 3; private static final int STATE_RELEASE = 4; private static final int TYPE_TOUCH_MATCH_VIEW = -1; private static final int TYPE_TOUCH = 0; private static final int TYPE_WAVE = 1; private static final float[] GRADIENT_STOPS = new float[]{0f, 0.99f, 1f}; private static final float GRADIENT_RADIUS = 16; private RippleDrawable(Drawable backgroundDrawable, int backgroundAnimDuration, int backgroundColor, int rippleType, boolean delayClick, int maxRippleRadius, int rippleAnimDuration, int rippleColor, Interpolator inInterpolator, Interpolator outInterpolator, int type, int topLeftCornerRadius, int topRightCornerRadius, int bottomRightCornerRadius, int bottomLeftCornerRadius, int left, int top, int right, int bottom){ setBackgroundDrawable(backgroundDrawable); mBackgroundAnimDuration = backgroundAnimDuration; mBackgroundColor = backgroundColor; mRippleType = rippleType; setDelayClick(delayClick); mMaxRippleRadius = maxRippleRadius; mRippleAnimDuration = rippleAnimDuration; mRippleColor = rippleColor; if(mRippleType == TYPE_TOUCH && mMaxRippleRadius <= 0) mRippleType = TYPE_TOUCH_MATCH_VIEW; mInInterpolator = inInterpolator; mOutInterpolator = outInterpolator; setMask(type, topLeftCornerRadius, topRightCornerRadius, bottomRightCornerRadius, bottomLeftCornerRadius, left, top, right, bottom); mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mFillPaint.setStyle(Paint.Style.FILL); mShaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mShaderPaint.setStyle(Paint.Style.FILL); mBackground = new Path(); mBackgroundBounds = new RectF(); mRipplePoint = new PointF(); mMatrix = new Matrix(); mInShader = new RadialGradient(0, 0, GRADIENT_RADIUS, new int[]{mRippleColor, mRippleColor, 0}, GRADIENT_STOPS, Shader.TileMode.CLAMP); if(mRippleType == TYPE_WAVE) mOutShader = new RadialGradient(0, 0, GRADIENT_RADIUS, new int[]{0, ColorUtil.getColor(mRippleColor, 0f), mRippleColor}, GRADIENT_STOPS, Shader.TileMode.CLAMP); } public void setBackgroundDrawable(Drawable backgroundDrawable){ mBackgroundDrawable = backgroundDrawable; if(mBackgroundDrawable != null) mBackgroundDrawable.setBounds(getBounds()); } public boolean isDelayClick(){ return mDelayClick; } public void setDelayClick(boolean enable){ mDelayClick = enable; } public void setMask(int type, int topLeftCornerRadius, int topRightCornerRadius, int bottomRightCornerRadius, int bottomLeftCornerRadius, int left, int top, int right, int bottom){ mMask = new Mask(type, topLeftCornerRadius, topRightCornerRadius, bottomRightCornerRadius, bottomLeftCornerRadius, left, top, right, bottom); } @Override public void setAlpha(int alpha) { mAlpha = alpha; } @Override public void setColorFilter(ColorFilter filter) { mFillPaint.setColorFilter(filter); mShaderPaint.setColorFilter(filter); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } public long getClickDelayTime(){ if(mState == STATE_RELEASE_ON_HOLD) return (mDelayClick ? 2 : 1) * Math.max(mBackgroundAnimDuration, mRippleAnimDuration) - (SystemClock.uptimeMillis() - mStartTime); else if(mState == STATE_RELEASE) return (mDelayClick ? Math.max(mBackgroundAnimDuration, mRippleAnimDuration) : 0) - (SystemClock.uptimeMillis() - mStartTime); return -1; } private void setRippleState(int state){ if(mState != state){ mState = state; if(mState != STATE_OUT){ if(mState != STATE_HOVER) start(); else stop(); } else stop(); } } private boolean setRippleEffect(float x, float y, float radius){ if(mRipplePoint.x != x || mRipplePoint.y != y || mRippleRadius != radius){ mRipplePoint.set(x, y); mRippleRadius = radius; radius = mRippleRadius / GRADIENT_RADIUS; mMatrix.reset(); mMatrix.postTranslate(x, y); mMatrix.postScale(radius, radius, x, y); mInShader.setLocalMatrix(mMatrix); if(mOutShader != null) mOutShader.setLocalMatrix(mMatrix); return true; } return false; } @Override protected void onBoundsChange(Rect bounds) { if(mBackgroundDrawable != null) mBackgroundDrawable.setBounds(bounds); mBackgroundBounds.set(bounds.left + mMask.left, bounds.top + mMask.top, bounds.right - mMask.right, bounds.bottom - mMask.bottom); mBackground.reset(); switch (mMask.type) { case Mask.TYPE_OVAL: mBackground.addOval(mBackgroundBounds, Direction.CW); break; case Mask.TYPE_RECTANGLE: mBackground.addRoundRect(mBackgroundBounds, mMask.cornerRadius, Direction.CW); break; } } @Override public boolean isStateful() { return mBackgroundDrawable != null && mBackgroundDrawable.isStateful(); } @Override protected boolean onStateChange(int[] state) { return mBackgroundDrawable != null && mBackgroundDrawable.setState(state); } @Override public void draw(Canvas canvas) { if(mBackgroundDrawable != null) mBackgroundDrawable.draw(canvas); switch (mRippleType) { case TYPE_TOUCH: case TYPE_TOUCH_MATCH_VIEW: drawTouch(canvas); break; case TYPE_WAVE: drawWave(canvas); break; } } private void drawTouch(Canvas canvas){ if(mState != STATE_OUT){ if(mBackgroundAlphaPercent > 0){ mFillPaint.setColor(mBackgroundColor); mFillPaint.setAlpha(Math.round(mAlpha * mBackgroundAlphaPercent)); canvas.drawPath(mBackground, mFillPaint); } if(mRippleRadius > 0 && mRippleAlphaPercent > 0){ mShaderPaint.setAlpha(Math.round(mAlpha * mRippleAlphaPercent)); mShaderPaint.setShader(mInShader); canvas.drawPath(mBackground, mShaderPaint); } } } private void drawWave(Canvas canvas){ if(mState != STATE_OUT){ if(mState == STATE_RELEASE){ if(mRippleRadius == 0){ mFillPaint.setColor(mRippleColor); canvas.drawPath(mBackground, mFillPaint); } else{ mShaderPaint.setShader(mOutShader); canvas.drawPath(mBackground, mShaderPaint); } } else if(mRippleRadius > 0){ mShaderPaint.setShader(mInShader); canvas.drawPath(mBackground, mShaderPaint); } } } private int getMaxRippleRadius(float x, float y){ float x1 = x < mBackgroundBounds.centerX() ? mBackgroundBounds.right : mBackgroundBounds.left; float y1 = y < mBackgroundBounds.centerY() ? mBackgroundBounds.bottom : mBackgroundBounds.top; return (int)Math.round(Math.sqrt(Math.pow(x1 - x, 2) + Math.pow(y1 - y, 2))); } @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: if(mState == STATE_OUT || mState == STATE_RELEASE){ if(mRippleType == TYPE_WAVE || mRippleType == TYPE_TOUCH_MATCH_VIEW) mMaxRippleRadius = getMaxRippleRadius(event.getX(), event.getY()); setRippleEffect(event.getX(), event.getY(), 0); setRippleState(STATE_PRESS); } else if(mRippleType == TYPE_TOUCH){ if(setRippleEffect(event.getX(), event.getY(), mRippleRadius)) invalidateSelf(); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if(mState != STATE_OUT){ if(mState == STATE_HOVER){ if(mRippleType == TYPE_WAVE || mRippleType == TYPE_TOUCH_MATCH_VIEW) setRippleEffect(mRipplePoint.x, mRipplePoint.y, 0); setRippleState(STATE_RELEASE); } else setRippleState(STATE_RELEASE_ON_HOLD); } break; } return true; } //Animation: based on http://cyrilmottier.com/2012/11/27/actionbar-on-the-move/ public void cancel(){ setRippleState(STATE_OUT); } private void resetAnimation(){ mStartTime = SystemClock.uptimeMillis(); } @Override public void start() { if(isRunning()) return; resetAnimation(); scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); invalidateSelf(); } @Override public void stop() { if(!isRunning()) return; mRunning = false; unscheduleSelf(mUpdater); invalidateSelf(); } @Override public boolean isRunning() { return mRunning; } @Override public void scheduleSelf(Runnable what, long when) { mRunning = true; super.scheduleSelf(what, when); } private final Runnable mUpdater = new Runnable() { @Override public void run() { switch (mRippleType) { case TYPE_TOUCH: case TYPE_TOUCH_MATCH_VIEW: updateTouch(); break; case TYPE_WAVE: updateWave(); break; } } }; private void updateTouch(){ if(mState != STATE_RELEASE){ float backgroundProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mBackgroundAnimDuration); mBackgroundAlphaPercent = mInInterpolator.getInterpolation(backgroundProgress) * Color.alpha(mBackgroundColor) / 255f; float touchProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mRippleAnimDuration); mRippleAlphaPercent = mInInterpolator.getInterpolation(touchProgress); setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * mInInterpolator.getInterpolation(touchProgress)); if(backgroundProgress == 1f && touchProgress == 1f){ mStartTime = SystemClock.uptimeMillis(); setRippleState(mState == STATE_PRESS ? STATE_HOVER : STATE_RELEASE); } } else{ float backgroundProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mBackgroundAnimDuration); mBackgroundAlphaPercent = (1f - mOutInterpolator.getInterpolation(backgroundProgress)) * Color.alpha(mBackgroundColor) / 255f; float touchProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mRippleAnimDuration); mRippleAlphaPercent = 1f - mOutInterpolator.getInterpolation(touchProgress); setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * (1f + 0.5f * mOutInterpolator.getInterpolation(touchProgress))); if(backgroundProgress == 1f && touchProgress == 1f) setRippleState(STATE_OUT); } if(isRunning()) scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); invalidateSelf(); } private void updateWave(){ float progress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mRippleAnimDuration); if(mState != STATE_RELEASE){ setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * mInInterpolator.getInterpolation(progress)); if(progress == 1f){ mStartTime = SystemClock.uptimeMillis(); if(mState == STATE_PRESS) setRippleState(STATE_HOVER); else{ setRippleEffect(mRipplePoint.x, mRipplePoint.y, 0); setRippleState(STATE_RELEASE); } } } else{ setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * mOutInterpolator.getInterpolation(progress)); if(progress == 1f) setRippleState(STATE_OUT); } if(isRunning()) scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); invalidateSelf(); } public static class Mask{ public static final int TYPE_RECTANGLE = 0; public static final int TYPE_OVAL = 1; final int type; final float[] cornerRadius = new float[8]; final int left; final int top; final int right; final int bottom; public Mask(int type, int topLeftCornerRadius, int topRightCornerRadius, int bottomRightCornerRadius, int bottomLeftCornerRadius, int left, int top, int right, int bottom){ this.type = type; cornerRadius[0] = topLeftCornerRadius; cornerRadius[1] = topLeftCornerRadius; cornerRadius[2] = topRightCornerRadius; cornerRadius[3] = topRightCornerRadius; cornerRadius[4] = bottomRightCornerRadius; cornerRadius[5] = bottomRightCornerRadius; cornerRadius[6] = bottomLeftCornerRadius; cornerRadius[7] = bottomLeftCornerRadius; this.left = left; this.top = top; this.right = right; this.bottom = bottom; } } public static class Builder{ private Drawable mBackgroundDrawable; private int mBackgroundAnimDuration = 200; private int mBackgroundColor; private int mRippleType; private int mMaxRippleRadius; private int mRippleAnimDuration = 400; private int mRippleColor; private boolean mDelayClick; private Interpolator mInInterpolator; private Interpolator mOutInterpolator; private int mMaskType; private int mMaskTopLeftCornerRadius; private int mMaskTopRightCornerRadius; private int mMaskBottomLeftCornerRadius; private int mMaskBottomRightCornerRadius; private int mMaskLeft; private int mMaskTop; private int mMaskRight; private int mMaskBottom; public Builder(){} public Builder(Context context, int defStyleRes){ this(context, null, 0, defStyleRes); } public Builder(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RippleDrawable, defStyleAttr, defStyleRes); int type, resId; backgroundColor(a.getColor(R.styleable.RippleDrawable_rd_backgroundColor, 0)); backgroundAnimDuration(a.getInteger(R.styleable.RippleDrawable_rd_backgroundAnimDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime))); rippleType(a.getInteger(R.styleable.RippleDrawable_rd_rippleType, RippleDrawable.TYPE_TOUCH)); delayClick(a.getBoolean(R.styleable.RippleDrawable_rd_delayClick, false)); type = ThemeUtil.getType(a, R.styleable.RippleDrawable_rd_maxRippleRadius); if(type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) maxRippleRadius(a.getInteger(R.styleable.RippleDrawable_rd_maxRippleRadius, -1)); else maxRippleRadius(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_maxRippleRadius, ThemeUtil.dpToPx(context, 48))); rippleColor(a.getColor(R.styleable.RippleDrawable_rd_rippleColor, ThemeUtil.colorControlHighlight(context, 0))); rippleAnimDuration(a.getInteger(R.styleable.RippleDrawable_rd_rippleAnimDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime))); if((resId = a.getResourceId(R.styleable.RippleDrawable_rd_inInterpolator, 0)) != 0) inInterpolator(AnimationUtils.loadInterpolator(context, resId)); if((resId = a.getResourceId(R.styleable.RippleDrawable_rd_outInterpolator, 0)) != 0) outInterpolator(AnimationUtils.loadInterpolator(context, resId)); maskType(a.getInteger(R.styleable.RippleDrawable_rd_maskType, Mask.TYPE_RECTANGLE)); cornerRadius(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_cornerRadius, 0)); topLeftCornerRadius(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_topLeftCornerRadius, mMaskTopLeftCornerRadius)); topRightCornerRadius(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_topRightCornerRadius, mMaskTopRightCornerRadius)); bottomRightCornerRadius(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_bottomRightCornerRadius, mMaskBottomRightCornerRadius)); bottomLeftCornerRadius(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_bottomLeftCornerRadius, mMaskBottomLeftCornerRadius)); padding(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_padding, 0)); left(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_leftPadding, mMaskLeft)); right(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_rightPadding, mMaskRight)); top(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_topPadding, mMaskTop)); bottom(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_bottomPadding, mMaskBottom)); a.recycle(); } public RippleDrawable build(){ if(mInInterpolator == null) mInInterpolator = new AccelerateInterpolator(); if(mOutInterpolator == null) mOutInterpolator = new DecelerateInterpolator(); return new RippleDrawable(mBackgroundDrawable, mBackgroundAnimDuration, mBackgroundColor, mRippleType, mDelayClick, mMaxRippleRadius, mRippleAnimDuration, mRippleColor, mInInterpolator, mOutInterpolator, mMaskType, mMaskTopLeftCornerRadius, mMaskTopRightCornerRadius, mMaskBottomRightCornerRadius, mMaskBottomLeftCornerRadius, mMaskLeft, mMaskTop, mMaskRight, mMaskBottom); } public Builder backgroundDrawable(Drawable drawable){ mBackgroundDrawable = drawable; return this; } public Builder backgroundAnimDuration(int duration){ mBackgroundAnimDuration = duration; return this; } public Builder backgroundColor(int color){ mBackgroundColor = color; return this; } public Builder rippleType(int type){ mRippleType = type; return this; } public Builder delayClick(boolean enable){ mDelayClick = enable; return this; } public Builder maxRippleRadius(int radius){ mMaxRippleRadius = radius; return this; } public Builder rippleAnimDuration(int duration){ mRippleAnimDuration = duration; return this; } public Builder rippleColor(int color){ mRippleColor = color; return this; } public Builder inInterpolator(Interpolator interpolator){ mInInterpolator = interpolator; return this; } public Builder outInterpolator(Interpolator interpolator){ mOutInterpolator = interpolator; return this; } public Builder maskType(int type){ mMaskType = type; return this; } public Builder cornerRadius(int radius){ mMaskTopLeftCornerRadius = radius; mMaskTopRightCornerRadius = radius; mMaskBottomLeftCornerRadius = radius; mMaskBottomRightCornerRadius = radius; return this; } public Builder topLeftCornerRadius(int radius){ mMaskTopLeftCornerRadius = radius; return this; } public Builder topRightCornerRadius(int radius){ mMaskTopRightCornerRadius = radius; return this; } public Builder bottomLeftCornerRadius(int radius){ mMaskBottomLeftCornerRadius = radius; return this; } public Builder bottomRightCornerRadius(int radius){ mMaskBottomRightCornerRadius = radius; return this; } public Builder padding(int padding){ mMaskLeft = padding; mMaskTop = padding; mMaskRight = padding; mMaskBottom = padding; return this; } public Builder left(int padding){ mMaskLeft = padding; return this; } public Builder top(int padding){ mMaskTop = padding; return this; } public Builder right(int padding){ mMaskRight = padding; return this; } public Builder bottom(int padding){ mMaskBottom = padding; return this; } } }
ToolbarRippleDrawable.java
package com.rey.material.drawable; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Path.Direction; import android.graphics.PixelFormat; import android.graphics.PointF; import android.graphics.RadialGradient; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.os.SystemClock; import android.util.AttributeSet; import android.view.animation.AccelerateInterpolator; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import com.example.buttonformaterialdesign.R; import com.rey.material.util.ColorUtil; import com.rey.material.util.ThemeUtil; import com.rey.material.util.ViewUtil; public class ToolbarRippleDrawable extends Drawable implements Animatable { private boolean mRunning = false; private Paint mShaderPaint; private Paint mFillPaint; private RadialGradient mInShader; private RadialGradient mOutShader; private Matrix mMatrix; private int mAlpha = 255; private RectF mBackgroundBounds; private Path mBackground; private int mBackgroundAnimDuration; private int mBackgroundColor; private float mBackgroundAlphaPercent; private PointF mRipplePoint; private float mRippleRadius; private int mRippleType; private int mMaxRippleRadius; private int mRippleAnimDuration; private int mRippleColor; private float mRippleAlphaPercent; private boolean mDelayClick; private Interpolator mInInterpolator; private Interpolator mOutInterpolator; private long mStartTime; private boolean mPressed = false; private int mState = STATE_OUT; private static final int STATE_OUT = 0; private static final int STATE_PRESS = 1; private static final int STATE_HOVER = 2; private static final int STATE_RELEASE_ON_HOLD = 3; private static final int STATE_RELEASE = 4; private static final int TYPE_TOUCH_MATCH_VIEW = -1; private static final int TYPE_TOUCH = 0; private static final int TYPE_WAVE = 1; private static final float[] GRADIENT_STOPS = new float[]{0f, 0.99f, 1f}; private static final float GRADIENT_RADIUS = 16; private ToolbarRippleDrawable(int backgroundAnimDuration, int backgroundColor, int rippleType, boolean delayClick, int maxTouchRadius, int touchAnimDuration, int touchColor, Interpolator inInterpolator, Interpolator outInterpolator){ mBackgroundAnimDuration = backgroundAnimDuration; mBackgroundColor = backgroundColor; mRippleType = rippleType; mMaxRippleRadius = maxTouchRadius; mRippleAnimDuration = touchAnimDuration; mRippleColor = touchColor; mDelayClick = delayClick; if(mRippleType == TYPE_TOUCH && mMaxRippleRadius <= 0) mRippleType = TYPE_TOUCH_MATCH_VIEW; mInInterpolator = inInterpolator; mOutInterpolator = outInterpolator; mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mFillPaint.setStyle(Paint.Style.FILL); mShaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mShaderPaint.setStyle(Paint.Style.FILL); mBackground = new Path(); mBackgroundBounds = new RectF(); mRipplePoint = new PointF(); mMatrix = new Matrix(); mInShader = new RadialGradient(0, 0, GRADIENT_RADIUS, new int[]{mRippleColor, mRippleColor, 0}, GRADIENT_STOPS, Shader.TileMode.CLAMP); if(mRippleType == TYPE_WAVE) mOutShader = new RadialGradient(0, 0, GRADIENT_RADIUS, new int[]{0, ColorUtil.getColor(mRippleColor, 0f), mRippleColor}, GRADIENT_STOPS, Shader.TileMode.CLAMP); } public boolean isDelayClick(){ return mDelayClick; } public void setDelayClick(boolean enable){ mDelayClick = enable; } @Override public void setAlpha(int alpha) { mAlpha = alpha; } @Override public void setColorFilter(ColorFilter filter) { mFillPaint.setColorFilter(filter); mShaderPaint.setColorFilter(filter); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } public long getClickDelayTime(){ if(mState == STATE_RELEASE_ON_HOLD) return (mDelayClick ? 2 : 1) * Math.max(mBackgroundAnimDuration, mRippleAnimDuration) - (SystemClock.uptimeMillis() - mStartTime); else if(mState == STATE_RELEASE) return (mDelayClick ? Math.max(mBackgroundAnimDuration, mRippleAnimDuration) : 0) - (SystemClock.uptimeMillis() - mStartTime); return -1; } private void setRippleState(int state){ if(mState != state){ mState = state; if(mState != STATE_OUT){ if(mState != STATE_HOVER) start(); else stop(); } else stop(); } } private boolean setRippleEffect(float x, float y, float radius){ if(mRipplePoint.x != x || mRipplePoint.y != y || mRippleRadius != radius){ mRipplePoint.set(x, y); mRippleRadius = radius; radius = mRippleRadius / GRADIENT_RADIUS; mMatrix.reset(); mMatrix.postTranslate(x, y); mMatrix.postScale(radius, radius, x, y); mInShader.setLocalMatrix(mMatrix); if(mOutShader != null) mOutShader.setLocalMatrix(mMatrix); return true; } return false; } @Override protected void onBoundsChange(Rect bounds) { mBackgroundBounds.set(bounds.left, bounds.top, bounds.right, bounds.bottom); mBackground.reset(); mBackground.addRect(mBackgroundBounds, Direction.CW); } @Override public boolean isStateful() { return true; } @Override protected boolean onStateChange(int[] state) { boolean pressed = ViewUtil.hasState(state, android.R.attr.state_pressed); if(mPressed != pressed){ mPressed = pressed; if(mPressed){ Rect bounds = getBounds(); if(mState == STATE_OUT || mState == STATE_RELEASE){ if(mRippleType == TYPE_WAVE || mRippleType == TYPE_TOUCH_MATCH_VIEW) mMaxRippleRadius = getMaxRippleRadius(bounds.exactCenterX(), bounds.exactCenterY()); setRippleEffect(bounds.exactCenterX(), bounds.exactCenterY(), 0); setRippleState(STATE_PRESS); } else if(mRippleType == TYPE_TOUCH) setRippleEffect(bounds.exactCenterX(), bounds.exactCenterY(), mRippleRadius); } else{ if(mState != STATE_OUT){ if(mState == STATE_HOVER){ if(mRippleType == TYPE_WAVE|| mRippleType == TYPE_TOUCH_MATCH_VIEW) setRippleEffect(mRipplePoint.x, mRipplePoint.y, 0); setRippleState(STATE_RELEASE); } else setRippleState(STATE_RELEASE_ON_HOLD); } } return true; } return false; } @Override public void draw(Canvas canvas) { switch (mRippleType) { case TYPE_TOUCH: case TYPE_TOUCH_MATCH_VIEW: drawTouch(canvas); break; case TYPE_WAVE: drawWave(canvas); break; } } private void drawTouch(Canvas canvas){ if(mState != STATE_OUT){ if(mBackgroundAlphaPercent > 0){ mFillPaint.setColor(mBackgroundColor); mFillPaint.setAlpha(Math.round(mAlpha * mBackgroundAlphaPercent)); canvas.drawPath(mBackground, mFillPaint); } if(mRippleRadius > 0 && mRippleAlphaPercent > 0){ mShaderPaint.setAlpha(Math.round(mAlpha * mRippleAlphaPercent)); mShaderPaint.setShader(mInShader); canvas.drawPath(mBackground, mShaderPaint); } } } private void drawWave(Canvas canvas){ if(mState != STATE_OUT){ if(mState == STATE_RELEASE){ if(mRippleRadius == 0){ mFillPaint.setColor(mRippleColor); canvas.drawPath(mBackground, mFillPaint); } else{ mShaderPaint.setShader(mOutShader); canvas.drawPath(mBackground, mShaderPaint); } } else if(mRippleRadius > 0){ mShaderPaint.setShader(mInShader); canvas.drawPath(mBackground, mShaderPaint); } } } private int getMaxRippleRadius(float x, float y){ float x1 = x < mBackgroundBounds.centerX() ? mBackgroundBounds.right : mBackgroundBounds.left; float y1 = y < mBackgroundBounds.centerY() ? mBackgroundBounds.bottom : mBackgroundBounds.top; return (int)Math.round(Math.sqrt(Math.pow(x1 - x, 2) + Math.pow(y1 - y, 2))); } //Animation: based on http://cyrilmottier.com/2012/11/27/actionbar-on-the-move/ public void cancel(){ setRippleState(STATE_OUT); } private void resetAnimation(){ mStartTime = SystemClock.uptimeMillis(); } @Override public void start() { if(isRunning()) return; resetAnimation(); scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); invalidateSelf(); } @Override public void stop() { if(!isRunning()) return; mRunning = false; unscheduleSelf(mUpdater); invalidateSelf(); } @Override public boolean isRunning() { return mRunning; } @Override public void scheduleSelf(Runnable what, long when) { mRunning = true; super.scheduleSelf(what, when); } private final Runnable mUpdater = new Runnable() { @Override public void run() { switch (mRippleType) { case TYPE_TOUCH: case TYPE_TOUCH_MATCH_VIEW: updateTouch(); break; case TYPE_WAVE: updateWave(); break; } } }; private void updateTouch(){ if(mState != STATE_RELEASE){ float backgroundProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mBackgroundAnimDuration); mBackgroundAlphaPercent = mInInterpolator.getInterpolation(backgroundProgress) * Color.alpha(mBackgroundColor) / 255f; float touchProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mRippleAnimDuration); mRippleAlphaPercent = mInInterpolator.getInterpolation(touchProgress); setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * mInInterpolator.getInterpolation(touchProgress)); if(backgroundProgress == 1f && touchProgress == 1f){ mStartTime = SystemClock.uptimeMillis(); setRippleState(mState == STATE_PRESS ? STATE_HOVER : STATE_RELEASE); } } else{ float backgroundProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mBackgroundAnimDuration); mBackgroundAlphaPercent = (1f - mOutInterpolator.getInterpolation(backgroundProgress)) * Color.alpha(mBackgroundColor) / 255f; float touchProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mRippleAnimDuration); mRippleAlphaPercent = 1f - mOutInterpolator.getInterpolation(touchProgress); setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * (1f + 0.5f * mOutInterpolator.getInterpolation(touchProgress))); if(backgroundProgress == 1f && touchProgress == 1f) setRippleState(STATE_OUT); } if(isRunning()) scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); invalidateSelf(); } private void updateWave(){ float progress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mRippleAnimDuration); if(mState != STATE_RELEASE){ setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * mInInterpolator.getInterpolation(progress)); if(progress == 1f){ mStartTime = SystemClock.uptimeMillis(); if(mState == STATE_PRESS) setRippleState(STATE_HOVER); else{ setRippleEffect(mRipplePoint.x, mRipplePoint.y, 0); setRippleState(STATE_RELEASE); } } } else{ setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * mOutInterpolator.getInterpolation(progress)); if(progress == 1f) setRippleState(STATE_OUT); } if(isRunning()) scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); invalidateSelf(); } public static class Builder{ private int mBackgroundAnimDuration = 200; private int mBackgroundColor; private int mRippleType; private int mMaxRippleRadius; private int mRippleAnimDuration = 400; private int mRippleColor; private boolean mDelayClick; private Interpolator mInInterpolator; private Interpolator mOutInterpolator; public Builder(){} public Builder(Context context, int defStyleRes){ this(context, null, 0, defStyleRes); } public Builder(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RippleDrawable, defStyleAttr, defStyleRes); int resId; backgroundColor(a.getColor(R.styleable.RippleDrawable_rd_backgroundColor, 0)); backgroundAnimDuration(a.getInteger(R.styleable.RippleDrawable_rd_backgroundAnimDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime))); rippleType(a.getInteger(R.styleable.RippleDrawable_rd_rippleType, ToolbarRippleDrawable.TYPE_TOUCH)); delayClick(a.getBoolean(R.styleable.RippleDrawable_rd_delayClick, false)); maxRippleRadius(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_maxRippleRadius, ThemeUtil.dpToPx(context, 48))); rippleColor(a.getColor(R.styleable.RippleDrawable_rd_rippleColor, ThemeUtil.colorControlHighlight(context, 0))); rippleAnimDuration(a.getInteger(R.styleable.RippleDrawable_rd_rippleAnimDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime))); if((resId = a.getResourceId(R.styleable.RippleDrawable_rd_inInterpolator, 0)) != 0) inInterpolator(AnimationUtils.loadInterpolator(context, resId)); if((resId = a.getResourceId(R.styleable.RippleDrawable_rd_outInterpolator, 0)) != 0) outInterpolator(AnimationUtils.loadInterpolator(context, resId)); a.recycle(); } public ToolbarRippleDrawable build(){ if(mInInterpolator == null) mInInterpolator = new AccelerateInterpolator(); if(mOutInterpolator == null) mOutInterpolator = new DecelerateInterpolator(); return new ToolbarRippleDrawable(mBackgroundAnimDuration, mBackgroundColor, mRippleType, mDelayClick, mMaxRippleRadius, mRippleAnimDuration, mRippleColor, mInInterpolator, mOutInterpolator); } public Builder backgroundAnimDuration(int duration){ mBackgroundAnimDuration = duration; return this; } public Builder backgroundColor(int color){ mBackgroundColor = color; return this; } public Builder rippleType(int type){ mRippleType = type; return this; } public Builder delayClick(boolean enable){ mDelayClick = enable; return this; } public Builder maxRippleRadius(int radius){ mMaxRippleRadius = radius; return this; } public Builder rippleAnimDuration(int duration){ mRippleAnimDuration = duration; return this; } public Builder rippleColor(int color){ mRippleColor = color; return this; } public Builder inInterpolator(Interpolator interpolator){ mInInterpolator = interpolator; return this; } public Builder outInterpolator(Interpolator interpolator){ mOutInterpolator = interpolator; return this; } } }
ColorUtil.java
package com.rey.material.util; import android.graphics.Color; public class ColorUtil { private static int getMiddleValue(int prev, int next, float factor){ return Math.round(prev + (next - prev) * factor); } public static int getMiddleColor(int prevColor, int curColor, float factor){ if(prevColor == curColor) return curColor; if(factor == 0f) return prevColor; else if(factor == 1f) return curColor; int a = getMiddleValue(Color.alpha(prevColor), Color.alpha(curColor), factor); int r = getMiddleValue(Color.red(prevColor), Color.red(curColor), factor); int g = getMiddleValue(Color.green(prevColor), Color.green(curColor), factor); int b = getMiddleValue(Color.blue(prevColor), Color.blue(curColor), factor); return Color.argb(a, r, g, b); } public static int getColor(int baseColor, float alphaPercent){ int alpha = Math.round(Color.alpha(baseColor) * alphaPercent); return (baseColor & 0x00FFFFFF) | (alpha << 24); } }
ThemeUtil.java
package com.rey.material.util; import android.annotation.TargetApi; import android.content.Context; import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.os.Build; import android.support.v7.internal.widget.TintTypedArray; import android.util.TypedValue; import com.example.buttonformaterialdesign.R; public class ThemeUtil { private static TypedValue value; public static int dpToPx(Context context, int dp){ return (int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics()) + 0.5f); } public static int spToPx(Context context, int sp){ return (int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, context.getResources().getDisplayMetrics()) + 0.5f); } private static int getColor(Context context, int id, int defaultValue){ if(value == null) value = new TypedValue(); try{ Theme theme = context.getTheme(); if(theme != null && theme.resolveAttribute(id, value, true)){ if (value.type >= TypedValue.TYPE_FIRST_INT && value.type <= TypedValue.TYPE_LAST_INT) return value.data; else if (value.type == TypedValue.TYPE_STRING) return context.getResources().getColor(value.resourceId); } } catch(Exception ex){} return defaultValue; } public static int windowBackground(Context context, int defaultValue){ return getColor(context, android.R.attr.windowBackground, defaultValue); } public static int textColorPrimary(Context context, int defaultValue){ return getColor(context, android.R.attr.textColorPrimary, defaultValue); } public static int textColorSecondary(Context context, int defaultValue){ return getColor(context, android.R.attr.textColorSecondary, defaultValue); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public static int colorPrimary(Context context, int defaultValue){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return getColor(context, android.R.attr.colorPrimary, defaultValue); return getColor(context, R.attr.colorPrimary, defaultValue); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public static int colorPrimaryDark(Context context, int defaultValue){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return getColor(context, android.R.attr.colorPrimaryDark, defaultValue); return getColor(context, R.attr.colorPrimaryDark, defaultValue); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public static int colorAccent(Context context, int defaultValue){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return getColor(context, android.R.attr.colorAccent, defaultValue); return getColor(context, R.attr.colorAccent, defaultValue); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public static int colorControlNormal(Context context, int defaultValue){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return getColor(context, android.R.attr.colorControlNormal, defaultValue); return getColor(context, R.attr.colorControlNormal, defaultValue); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public static int colorControlActivated(Context context, int defaultValue){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return getColor(context, android.R.attr.colorControlActivated, defaultValue); return getColor(context, R.attr.colorControlActivated, defaultValue); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public static int colorControlHighlight(Context context, int defaultValue){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return getColor(context, android.R.attr.colorControlHighlight, defaultValue); return getColor(context, R.attr.colorControlHighlight, defaultValue); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public static int colorButtonNormal(Context context, int defaultValue){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return getColor(context, android.R.attr.colorButtonNormal, defaultValue); return getColor(context, R.attr.colorButtonNormal, defaultValue); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public static int colorSwitchThumbNormal(Context context, int defaultValue){ return getColor(context, R.attr.colorSwitchThumbNormal, defaultValue); } public static int getType(TypedArray array, int index){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return array.getType(index); else{ TypedValue value = array.peekValue(index); return value == null ? TypedValue.TYPE_NULL : value.type; } } public static CharSequence getString(TypedArray array, int index, CharSequence defaultValue){ String result = array.getString(index); return result == null ? defaultValue : result; } public static CharSequence getString(TintTypedArray array, int index, CharSequence defaultValue){ String result = array.getString(index); return result == null ? defaultValue : result; } }
ViewUtil.java
package com.rey.material.util; import java.util.concurrent.atomic.AtomicInteger; import android.annotation.SuppressLint; public class ViewUtil { public static final long FRAME_DURATION = 1000 / 60; private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1); @SuppressLint("NewApi") public static int generateViewId() { if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { for (;;) { final int result = sNextGeneratedId.get(); // aapt-generated IDs have the high byte nonzero; clamp to the range under that. int newValue = result + 1; if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0. if (sNextGeneratedId.compareAndSet(result, newValue)) return result; } } else return android.view.View.generateViewId(); } public static boolean hasState(int[] states, int state){ if(states == null) return false; for (int state1 : states) if (state1 == state) return true; return false; } }
Button.java
package com.rey.material.widget; import android.content.Context; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.support.v7.internal.widget.TintManager; import android.util.AttributeSet; import android.view.MotionEvent; import com.rey.material.drawable.RippleDrawable; import java.lang.reflect.Field; public class Button extends android.widget.Button { private RippleManager mRippleManager = new RippleManager(); public Button(Context context) { super(context); init(context, null, 0, 0); } public Button(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public Button(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public Button(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, defStyleRes); } private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ applyStyle(context, attrs, defStyleAttr, defStyleRes); } public void applyStyle(int resId){ applyStyle(getContext(), null, 0, resId); } private void applyStyle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ mRippleManager.onCreate(this, context, attrs, defStyleAttr, defStyleRes); } @Override public void setBackgroundDrawable(Drawable drawable) { Drawable background = getBackground(); if(background instanceof RippleDrawable && !(drawable instanceof RippleDrawable)) ((RippleDrawable) background).setBackgroundDrawable(drawable); else super.setBackgroundDrawable(drawable); } @Override public void setOnClickListener(OnClickListener l) { if(l == mRippleManager) super.setOnClickListener(l); else{ mRippleManager.setOnClickListener(l); setOnClickListener(mRippleManager); } } @Override public boolean onTouchEvent(@NonNull MotionEvent event) { boolean result = super.onTouchEvent(event); return mRippleManager.onTouchEvent(event) || result; } }
FloatingActionButton.java
package com.rey.material.widget; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.RadialGradient; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.widget.FrameLayout; import android.widget.RelativeLayout; import com.example.buttonformaterialdesign.R; import com.rey.material.drawable.LineMorphingDrawable; import com.rey.material.drawable.RippleDrawable; import com.rey.material.util.ThemeUtil; import com.rey.material.util.ViewUtil; public class FloatingActionButton extends View { private OvalShadowDrawable mBackground; private Drawable mIcon; private Drawable mPrevIcon; private int mAnimDuration; private Interpolator mInterpolator; private SwitchIconAnimator mSwitchIconAnimator; private int mIconSize; private RippleManager mRippleManager = new RippleManager(); public static FloatingActionButton make(Context context, int resId){ return new FloatingActionButton(context, null, resId); } public FloatingActionButton(Context context) { super(context); init(context, null, 0, 0); } public FloatingActionButton(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, defStyleRes); } private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { setClickable(true); mSwitchIconAnimator = new SwitchIconAnimator(); applyStyle(context, attrs, defStyleAttr, defStyleRes); } public void applyStyle(int resId){ applyStyle(getContext(), null, 0, resId); } private void applyStyle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FloatingActionButton, defStyleAttr, defStyleRes); int radius = a.getDimensionPixelSize(R.styleable.FloatingActionButton_fab_radius, ThemeUtil.dpToPx(context, 28)); int elevation = a.getDimensionPixelSize(R.styleable.FloatingActionButton_fab_elevation, ThemeUtil.dpToPx(context, 4)); int bgColor = a.getColor(R.styleable.FloatingActionButton_fab_backgroundColor, ThemeUtil.colorAccent(context, 0xFFFAFAFA)); int iconSrc = a.getResourceId(R.styleable.FloatingActionButton_fab_iconSrc, 0); int iconLineMorphing = a.getResourceId(R.styleable.FloatingActionButton_fab_iconLineMorphing, 0); mIconSize = a.getDimensionPixelSize(R.styleable.FloatingActionButton_fab_iconSize, ThemeUtil.dpToPx(context, 24)); mAnimDuration = a.getInteger(R.styleable.FloatingActionButton_fab_animDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime)); int resId = a.getResourceId(R.styleable.FloatingActionButton_fab_interpolator, 0); if(resId != 0) mInterpolator = AnimationUtils.loadInterpolator(context, resId); else if(mInterpolator == null) mInterpolator = new DecelerateInterpolator(); a.recycle(); mBackground = new OvalShadowDrawable(radius, bgColor, elevation, elevation); mBackground.setBounds(0, 0, getWidth(), getHeight()); if(iconLineMorphing != 0) setIcon(new LineMorphingDrawable.Builder(context, iconLineMorphing).build(), false); else if(iconSrc != 0) setIcon(context.getResources().getDrawable(iconSrc), false); mRippleManager.onCreate(this, context, attrs, defStyleAttr, defStyleRes); Drawable background = getBackground(); if(background != null && background instanceof RippleDrawable){ RippleDrawable drawable = (RippleDrawable)background; drawable.setBackgroundDrawable(null); drawable.setMask(RippleDrawable.Mask.TYPE_OVAL, 0, 0, 0, 0, (int)mBackground.getPaddingLeft(), (int)mBackground.getPaddingTop(), (int)mBackground.getPaddingRight(), (int)mBackground.getPaddingBottom()); } setClickable(true); } public int getRadius(){ return mBackground.getRadius(); } public void setRadius(int radius){ if(mBackground.setRadius(radius)) requestLayout(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public float getElevation() { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return super.getElevation(); return mBackground.getShadowSize(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public void setElevation(float elevation) { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) super.setElevation(elevation); else if(mBackground.setShadow(elevation, elevation)) requestLayout(); } public int getLineMorphingState(){ if(mIcon != null && mIcon instanceof LineMorphingDrawable) return ((LineMorphingDrawable)mIcon).getLineState(); return -1; } public void setLineMorphingState(int state, boolean animation){ if(mIcon != null && mIcon instanceof LineMorphingDrawable) ((LineMorphingDrawable)mIcon).switchLineState(state, animation); } public int getBackgroundColor(){ return mBackground.getColor(); } public Drawable getIcon(){ return mIcon; } public void setIcon(Drawable icon, boolean animation){ if(icon == null) return; if(animation) { mSwitchIconAnimator.startAnimation(icon); invalidate(); } else{ if(mIcon != null){ mIcon.setCallback(null); unscheduleDrawable(mIcon); } mIcon = icon; float half = mIconSize / 2f; mIcon.setBounds((int)(mBackground.getCenterX() - half), (int)(mBackground.getCenterY() - half), (int)(mBackground.getCenterX() + half), (int)(mBackground.getCenterY() + half)); mIcon.setCallback(this); invalidate(); } } @Override public void setBackgroundColor(int color){ mBackground.setColor(color); invalidate(); } public void show(Activity activity, int x, int y, int gravity){ if(getParent() == null){ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(mBackground.getIntrinsicWidth(), mBackground.getIntrinsicHeight()); updateParams(x, y, gravity, params); activity.getWindow().addContentView(this, params); } else updateLocation(x, y, gravity); } public void show(ViewGroup parent, int x, int y, int gravity){ if(getParent() == null){ ViewGroup.LayoutParams params = parent.generateLayoutParams(null); params.width = mBackground.getIntrinsicWidth(); params.height = mBackground.getIntrinsicHeight(); updateParams(x, y, gravity, params); parent.addView(this, params); } else updateLocation(x, y, gravity); } public void updateLocation(int x, int y, int gravity){ if(getParent() != null) updateParams(x, y, gravity, getLayoutParams()); else Log.v(FloatingActionButton.class.getSimpleName(), "updateLocation() is called without parent"); } private void updateParams(int x, int y, int gravity, ViewGroup.LayoutParams params){ int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK; switch (horizontalGravity) { case Gravity.LEFT: setLeftMargin(params, (int)(x - mBackground.getPaddingLeft())); break; case Gravity.CENTER_HORIZONTAL: setLeftMargin(params, (int)(x - mBackground.getCenterX())); break; case Gravity.RIGHT: setLeftMargin(params, (int)(x - mBackground.getPaddingLeft() - mBackground.getRadius() * 2)); break; default: setLeftMargin(params, (int)(x - mBackground.getPaddingLeft())); break; } int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; switch (verticalGravity) { case Gravity.TOP: setTopMargin(params, (int)(y - mBackground.getPaddingTop())); break; case Gravity.CENTER_VERTICAL: setTopMargin(params, (int)(y - mBackground.getCenterY())); break; case Gravity.BOTTOM: setTopMargin(params, (int)(y - mBackground.getPaddingTop() - mBackground.getRadius() * 2)); break; default: setTopMargin(params, (int)(y - mBackground.getPaddingTop())); break; } setLayoutParams(params); } private void setLeftMargin(ViewGroup.LayoutParams params, int value){ if(params instanceof FrameLayout.LayoutParams) ((FrameLayout.LayoutParams)params).leftMargin = value; else if(params instanceof RelativeLayout.LayoutParams) ((RelativeLayout.LayoutParams)params).leftMargin = value; else Log.v(FloatingActionButton.class.getSimpleName(), "cannot recognize LayoutParams: " + params); } private void setTopMargin(ViewGroup.LayoutParams params, int value){ if(params instanceof FrameLayout.LayoutParams) ((FrameLayout.LayoutParams)params).topMargin = value; else if(params instanceof RelativeLayout.LayoutParams) ((RelativeLayout.LayoutParams)params).topMargin = value; else Log.v(FloatingActionButton.class.getSimpleName(), "cannot recognize LayoutParams: " + params); } public void dismiss(){ if(getParent() != null) ((ViewGroup)getParent()).removeView(this); } @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || mBackground == who || mIcon == who || mPrevIcon == who; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(mBackground.getIntrinsicWidth(), mBackground.getIntrinsicHeight()); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { mBackground.setBounds(0, 0, w, h); if(mIcon != null){ float half = mIconSize / 2f; mIcon.setBounds((int)(mBackground.getCenterX() - half), (int)(mBackground.getCenterY() - half), (int)(mBackground.getCenterX() + half), (int)(mBackground.getCenterY() + half)); } if(mPrevIcon != null){ float half = mIconSize / 2f; mPrevIcon.setBounds((int)(mBackground.getCenterX() - half), (int)(mBackground.getCenterY() - half), (int)(mBackground.getCenterX() + half), (int)(mBackground.getCenterY() + half)); } } @Override public void draw(@NonNull Canvas canvas) { mBackground.draw(canvas); super.draw(canvas); if(mPrevIcon != null) mPrevIcon.draw(canvas); if(mIcon != null) mIcon.draw(canvas); } @Override public void setOnClickListener(OnClickListener l) { if(l == mRippleManager) super.setOnClickListener(l); else{ mRippleManager.setOnClickListener(l); setOnClickListener(mRippleManager); } } @Override public boolean onTouchEvent(@NonNull MotionEvent event) { int action = event.getActionMasked(); if(action == MotionEvent.ACTION_DOWN && ! mBackground.isPointerOver(event.getX(), event.getY())) return false; boolean result = super.onTouchEvent(event); return mRippleManager.onTouchEvent(event) || result; } private class OvalShadowDrawable extends Drawable{ private Paint mShadowPaint; private Paint mGlowPaint; private Paint mPaint; private int mRadius; private float mShadowSize; private float mShadowOffset; private Path mShadowPath; private Path mGlowPath; private RectF mTempRect = new RectF(); private int mColor; private boolean mNeedBuildShadow = true; private static final int COLOR_SHADOW_START = 0x4C000000; private static final int COLOR_SHADOW_END = 0x00000000; public OvalShadowDrawable(int radius, int color, float shadowSize, float shadowOffset){ mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mPaint.setStyle(Paint.Style.FILL); setColor(color); setRadius(radius); setShadow(shadowSize, shadowOffset); } public boolean setRadius(int radius){ if(mRadius != radius){ mRadius = radius; mNeedBuildShadow = true; invalidateSelf(); return true; } return false; } public boolean setShadow(float size, float offset){ if(mShadowSize != size || mShadowOffset != offset){ mShadowSize = size; mShadowOffset = offset; mNeedBuildShadow = true; invalidateSelf(); return true; } return false; } public void setColor(int color){ if(mColor != color){ mColor = color; mPaint.setColor(mColor); invalidateSelf(); } } public int getColor(){ return mColor; } public int getRadius(){ return mRadius; } public float getShadowSize(){ return mShadowSize; } public float getShadowOffset(){ return mShadowOffset; } public float getPaddingLeft(){ return mShadowSize; } public float getPaddingTop(){ return mShadowSize; } public float getPaddingRight(){ return mShadowSize; } public float getPaddingBottom(){ return mShadowSize + mShadowOffset; } public float getCenterX(){ return mRadius + mShadowSize; } public float getCenterY(){ return mRadius + mShadowSize; } public boolean isPointerOver(float x, float y){ float distance = (float)Math.sqrt(Math.pow(x - getCenterX(), 2) + Math.pow(y - getCenterY(), 2)); return distance < mRadius; } @Override public int getIntrinsicWidth() { return (int)((mRadius + mShadowSize) * 2 + 0.5f); } @Override public int getIntrinsicHeight() { return (int)((mRadius + mShadowSize) * 2 + mShadowOffset + 0.5f); } private void buildShadow(){ if(mShadowSize <= 0) return; if(mShadowPaint == null){ mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mShadowPaint.setStyle(Paint.Style.FILL); mShadowPaint.setDither(true); } float startRatio = (float)mRadius / (mRadius + mShadowSize + mShadowOffset); mShadowPaint.setShader(new RadialGradient(0, 0, mRadius + mShadowSize, new int[]{COLOR_SHADOW_START, COLOR_SHADOW_START, COLOR_SHADOW_END}, new float[]{0f, startRatio, 1f} , Shader.TileMode.CLAMP)); if(mShadowPath == null){ mShadowPath = new Path(); mShadowPath.setFillType(Path.FillType.EVEN_ODD); } else mShadowPath.reset(); float radius = mRadius + mShadowSize; mTempRect.set(-radius, -radius, radius, radius); mShadowPath.addOval(mTempRect, Path.Direction.CW); radius = mRadius - 1; mTempRect.set(-radius, -radius - mShadowOffset, radius, radius - mShadowOffset); mShadowPath.addOval(mTempRect, Path.Direction.CW); if(mGlowPaint == null){ mGlowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mGlowPaint.setStyle(Paint.Style.FILL); mGlowPaint.setDither(true); } startRatio = (mRadius - mShadowSize / 2f) / (mRadius + mShadowSize / 2f); mGlowPaint.setShader(new RadialGradient(0, 0, mRadius + mShadowSize / 2f, new int[]{COLOR_SHADOW_START, COLOR_SHADOW_START, COLOR_SHADOW_END}, new float[]{0f, startRatio, 1f} , Shader.TileMode.CLAMP)); if(mGlowPath == null){ mGlowPath = new Path(); mGlowPath.setFillType(Path.FillType.EVEN_ODD); } else mGlowPath.reset(); radius = mRadius + mShadowSize / 2f; mTempRect.set(-radius, -radius, radius, radius); mGlowPath.addOval(mTempRect, Path.Direction.CW); radius = mRadius - 1; mTempRect.set(-radius, -radius, radius, radius); mGlowPath.addOval(mTempRect, Path.Direction.CW); } @Override public void draw(Canvas canvas) { if(mNeedBuildShadow){ buildShadow(); mNeedBuildShadow = false; } int saveCount; if(mShadowSize > 0){ saveCount = canvas.save(); canvas.translate(mShadowSize + mRadius, mShadowSize + mRadius + mShadowOffset); canvas.drawPath(mShadowPath, mShadowPaint); canvas.restoreToCount(saveCount); } saveCount = canvas.save(); canvas.translate(mShadowSize + mRadius, mShadowSize + mRadius); if(mShadowSize > 0) canvas.drawPath(mGlowPath, mGlowPaint); mTempRect.set(-mRadius, -mRadius, mRadius, mRadius); canvas.drawOval(mTempRect, mPaint); canvas.restoreToCount(saveCount); } @Override public void setAlpha(int alpha) { mShadowPaint.setAlpha(alpha); mPaint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter cf) { mShadowPaint.setColorFilter(cf); mPaint.setColorFilter(cf); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } } @Override protected Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); ss.state = getLineMorphingState(); return ss; } @Override protected void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); if(ss.state >= 0) setLineMorphingState(ss.state, false); requestLayout(); } static class SavedState extends BaseSavedState { int state; /** * Constructor called from {@link Slider#onSaveInstanceState()} */ SavedState(Parcelable superState) { super(superState); } /** * Constructor called from {@link #CREATOR} */ private SavedState(Parcel in) { super(in); state = in.readInt(); } @Override public void writeToParcel(@NonNull Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(state); } @Override public String toString() { return "FloatingActionButton.SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " state=" + state + "}"; } public static final Creator<SavedState> CREATOR = new Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; } class SwitchIconAnimator implements Runnable{ boolean mRunning = false; long mStartTime; public void resetAnimation(){ mStartTime = SystemClock.uptimeMillis(); mIcon.setAlpha(0); mPrevIcon.setAlpha(255); } public boolean startAnimation(Drawable icon) { if(mIcon == icon) return false; mPrevIcon = mIcon; mIcon = icon; float half = mIconSize / 2f; mIcon.setBounds((int)(mBackground.getCenterX() - half), (int)(mBackground.getCenterY() - half), (int)(mBackground.getCenterX() + half), (int)(mBackground.getCenterY() + half)); mIcon.setCallback(FloatingActionButton.this); if(getHandler() != null){ resetAnimation(); mRunning = true; getHandler().postAtTime(this, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); } else { mPrevIcon.setCallback(null); unscheduleDrawable(mPrevIcon); mPrevIcon = null; } invalidate(); return true; } public void stopAnimation() { mRunning = false; mPrevIcon.setCallback(null); unscheduleDrawable(mPrevIcon); mPrevIcon = null; mIcon.setAlpha(255); if(getHandler() != null) getHandler().removeCallbacks(this); invalidate(); } @Override public void run() { long curTime = SystemClock.uptimeMillis(); float progress = Math.min(1f, (float)(curTime - mStartTime) / mAnimDuration); float value = mInterpolator.getInterpolation(progress); mIcon.setAlpha(Math.round(255 * value)); mPrevIcon.setAlpha(Math.round(255 * (1f - value))); if(progress == 1f) stopAnimation(); if(mRunning) { if(getHandler() != null) getHandler().postAtTime(this, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); else stopAnimation(); } invalidate(); } } }
RippleManager.java
package com.rey.material.widget; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import com.example.buttonformaterialdesign.R; import com.rey.material.drawable.RippleDrawable; import com.rey.material.drawable.ToolbarRippleDrawable; public final class RippleManager implements View.OnClickListener, Runnable{ private View.OnClickListener mClickListener; private View mView; public RippleManager(){} @SuppressWarnings("deprecation") @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public void onCreate(View v, Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ if(v.isInEditMode()) return; mView = v; TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RippleView, defStyleAttr, defStyleRes); int rippleStyle = a.getResourceId(R.styleable.RippleView_rd_style, 0); RippleDrawable drawable = null; if(rippleStyle != 0) drawable = new RippleDrawable.Builder(context, rippleStyle).backgroundDrawable(mView.getBackground()).build(); else{ boolean rippleEnable = a.getBoolean(R.styleable.RippleView_rd_enable, false); if(rippleEnable) drawable = new RippleDrawable.Builder(context, attrs, defStyleAttr, defStyleRes).backgroundDrawable(mView.getBackground()).build(); } a.recycle(); if(drawable != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) mView.setBackground(drawable); else mView.setBackgroundDrawable(drawable); } } public boolean isDelayClick(){ Drawable background = mView.getBackground(); if(background instanceof RippleDrawable) return ((RippleDrawable)background).isDelayClick(); else if(background instanceof ToolbarRippleDrawable) return ((ToolbarRippleDrawable)background).isDelayClick(); return false; } public void setDelayClick(boolean delay){ Drawable background = mView.getBackground(); if(background instanceof RippleDrawable) ((RippleDrawable)background).setDelayClick(delay); else if(background instanceof ToolbarRippleDrawable) ((ToolbarRippleDrawable)background).setDelayClick(delay); } public void setOnClickListener(View.OnClickListener l) { mClickListener = l; } public boolean onTouchEvent(MotionEvent event){ Drawable background = mView.getBackground(); return background instanceof RippleDrawable && ((RippleDrawable) background).onTouch(mView, event); } @Override public void onClick(View v) { Drawable background = mView.getBackground(); long delay = 0; if(background instanceof RippleDrawable) delay = ((RippleDrawable)background).getClickDelayTime(); else if(background instanceof ToolbarRippleDrawable) delay = ((ToolbarRippleDrawable)background).getClickDelayTime(); if(delay > 0 && mView.getHandler() != null) mView.getHandler().postDelayed(this, delay); else run(); } @Override public void run() { if(mClickListener != null) mClickListener.onClick(mView); } public static void cancelRipple(View v){ Drawable background = v.getBackground(); if(background instanceof RippleDrawable) ((RippleDrawable)background).cancel(); else if(background instanceof ToolbarRippleDrawable) ((ToolbarRippleDrawable)background).cancel(); if(v instanceof ViewGroup){ ViewGroup vg = (ViewGroup)v; for(int i = 0, count = vg.getChildCount(); i < count; i++) RippleManager.cancelRipple(vg.getChildAt(i)); } } }
—> Run Your Code.
Leave a Reply