Android Switches 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.switchesformaterialdesign" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="21" /> <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>
attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="WeekView"> <attr name="wv_backgroundColor" format="reference|color"/> <attr name="wv_verticalPadding" format="reference|dimension"/> <attr name="wv_horizontalPadding" format="reference|dimension"/> <attr name="wv_animDuration" format="reference|integer"/> </declare-styleable> <declare-styleable name="ContactView"> <attr name="cv_buttonSrc" format="reference"/> <attr name="cv_buttonSize" format="reference|dimension"/> <attr name="cv_avatarSrc" format="reference"/> <attr name="cv_avatarSize" format="reference|dimension"/> <attr name="cv_spacing" format="reference|dimension"/> <attr name="cv_name" format="reference|string"/> <attr name="cv_nameTextAppearance" format="reference"/> <attr name="cv_nameTextSize" format="reference|dimension"/> <attr name="cv_nameTextColor" format="reference|color"/> <attr name="cv_address" format="reference|string"/> <attr name="cv_addressTextAppearance" format="reference"/> <attr name="cv_addressTextSize" format="reference|dimension"/> <attr name="cv_addressTextColor" format="reference|color"/> <attr name="android:minHeight" /> </declare-styleable> <declare-styleable name="ContactEditText"> <attr name="cet_spanHeight" format="reference|dimension"/> <attr name="cet_spanMaxWidth" format="reference|dimension"/> <attr name="cet_spanPaddingLeft" format="reference|dimension"/> <attr name="cet_spanPaddingRight" format="reference|dimension"/> <attr name="cet_spanFontFamily" format="reference|string"/> <attr name="cet_spanTextStyle" format="integer"> <enum name="normal" value="0" /> <enum name="bold" value="1" /> <enum name="italic" value="2" /> <enum name="bold_italic" value="3" /> </attr> <attr name="cet_spanTextSize" format="reference|dimension"/> <attr name="cet_spanTextColor" format="reference|color"/> <attr name="cet_spanBackgroundColor" format="reference|color"/> <attr name="cet_spanSpacing" format="reference|dimension"/> </declare-styleable> <attr name="pv_progressMode" format="integer"> <enum name="determinate" value="0x00000000" /> <enum name="indeterminate" value="0x00000001" /> <enum name="buffer" value="0x00000002" /> <enum name="query" value="0x00000003" /> </attr> <attr name="pv_progress" format="float"/> <attr name="pv_secondaryProgress" format="float"/> <attr name="rd_style" format="reference"/> <attr name="rd_enable" format="boolean"/> <declare-styleable name="CircularProgressDrawable"> <attr name="cpd_padding" format="reference|dimension"/> <attr name="cpd_initialAngle" format="reference|integer"/> <attr name="cpd_maxSweepAngle" format="reference|integer"/> <attr name="cpd_minSweepAngle" format="reference|integer"/> <attr name="cpd_strokeSize" format="reference|dimension"/> <attr name="cpd_strokeColor" format="reference|color"/> <attr name="cpd_strokeSecondaryColor" format="reference|color"/> <attr name="cpd_strokeColors" format="reference"/> <attr name="cpd_reverse" format="boolean"/> <attr name="cpd_rotateDuration" format="reference|integer"/> <attr name="cpd_transformDuration" format="reference|integer"/> <attr name="cpd_keepDuration" format="reference|integer"/> <attr name="cpd_transformInterpolator" format="reference"/> <attr name="cpd_inAnimDuration" format="reference|integer"/> <attr name="cpd_outAnimDuration" format="reference|integer"/> <attr name="cpd_inStepColors" format="reference"/> <attr name="cpd_inStepPercent" format="float"/> <attr name="pv_progressMode"/> <attr name="pv_progress"/> <attr name="pv_secondaryProgress"/> </declare-styleable> <declare-styleable name="LinearProgressDrawable"> <attr name="lpd_maxLineWidth" format="reference|dimension|fraction"/> <attr name="lpd_minLineWidth" format="reference|dimension|fraction"/> <attr name="lpd_strokeSize" format="reference|dimension"/> <attr name="lpd_strokeColor" format="reference|color"/> <attr name="lpd_strokeSecondaryColor" format="reference|color"/> <attr name="lpd_strokeColors" format="reference"/> <attr name="lpd_reverse" format="boolean"/> <attr name="lpd_travelDuration" format="reference|integer"/> <attr name="lpd_transformDuration" format="reference|integer"/> <attr name="lpd_keepDuration" format="reference|integer"/> <attr name="lpd_transformInterpolator" format="reference"/> <attr name="lpd_inAnimDuration" format="reference|integer"/> <attr name="lpd_outAnimDuration" format="reference|integer"/> <attr name="lpd_verticalAlign" format="integer"> <enum name="top" value="0x00000000" /> <enum name="center" value="0x00000001" /> <enum name="bottom" value="0x00000002" /> </attr> <attr name="pv_progressMode"/> <attr name="pv_progress"/> <attr name="pv_secondaryProgress"/> </declare-styleable> <declare-styleable name="ProgressView"> <attr name="pv_autostart" format="boolean"/> <attr name="pv_circular" format="boolean"/> <attr name="pv_progressStyle" format="reference"/> </declare-styleable> <declare-styleable name="RippleView"> <attr name="rd_style" /> <attr name="rd_enable" /> </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> <declare-styleable name="CheckBoxDrawable"> <attr name="cbd_width" format="reference|dimension"/> <attr name="cbd_height" format="reference|dimension"/> <attr name="cbd_boxSize" format="reference|dimension"/> <attr name="cbd_cornerRadius" format="reference|dimension"/> <attr name="cbd_strokeSize" format="reference|dimension"/> <attr name="cbd_strokeColor" format="reference|color"/> <attr name="cbd_tickColor" format="reference|color"/> <attr name="cbd_animDuration" format="reference|integer"/> </declare-styleable> <declare-styleable name="Switch"> <attr name="sw_trackSize" format="reference|dimension"/> <attr name="sw_trackColor" format="reference|color"/> <attr name="sw_trackCap" format="integer"> <enum name="butt" value="0x00000000" /> <enum name="round" value="0x00000001" /> <enum name="square" value="0x00000002" /> </attr> <attr name="sw_thumbColor" format="reference|color"/> <attr name="sw_thumbRadius" format="reference|dimension"/> <attr name="sw_thumbElevation" format="reference|dimension"/> <attr name="sw_animDuration" format="reference|integer"/> <attr name="sw_interpolator" format="reference"/> <attr name="android:gravity" /> <attr name="android:checked" /> </declare-styleable> <declare-styleable name="Slider"> <attr name="sl_trackSize" format="reference|dimension"/> <attr name="sl_primaryColor" format="reference|color"/> <attr name="sl_secondaryColor" format="reference|color"/> <attr name="sl_trackCap" format="integer"> <enum name="butt" value="0x00000000" /> <enum name="round" value="0x00000001" /> <enum name="square" value="0x00000002" /> </attr> <attr name="sl_thumbBorderSize" format="reference|dimension"/> <attr name="sl_thumbRadius" format="reference|dimension"/> <attr name="sl_thumbFocusRadius" format="reference|dimension"/> <attr name="sl_travelAnimDuration" format="reference|integer"/> <attr name="sl_transformAnimDuration" format="reference|integer"/> <attr name="sl_interpolator" format="reference"/> <attr name="sl_minValue" format="reference|integer"/> <attr name="sl_maxValue" format="reference|integer"/> <attr name="sl_stepValue" format="reference|integer"/> <attr name="sl_value" format="reference|integer"/> <attr name="sl_discreteMode" format="reference|boolean"/> <attr name="sl_fontFamily" format="string|reference"/> <attr name="sl_textStyle" format="integer"> <enum name="normal" value="0" /> <enum name="bold" value="1" /> <enum name="italic" value="2" /> <enum name="bold_italic" value="3" /> </attr> <attr name="sl_textSize" format="reference|dimension"/> <attr name="sl_textColor" format="reference|color"/> <attr name="android:gravity" /> <attr name="android:enabled" /> </declare-styleable> <declare-styleable name="NavigationDrawerDrawable"> <attr name="nd_ripple" format="reference"/> <attr name="nd_icon" format="reference"/> </declare-styleable> <declare-styleable name="TabPageIndicator"> <attr name="tpi_tabPadding" format="reference|dimension"/> <attr name="tpi_tabRipple" format="reference"/> <attr name="tpi_indicatorColor" format="reference|color"/> <attr name="tpi_indicatorHeight" format="reference|dimension"/> <attr name="android:textAppearance" /> <attr name="tpi_mode" format="integer"> <enum name="scroll" value="0x00000000" /> <enum name="fixed" value="0x00000001" /> </attr> </declare-styleable> <declare-styleable name="EditText"> <attr name="et_inputId" format="reference"/> <attr name="et_labelEnable" format="boolean"/> <attr name="et_labelPadding" format="reference|dimension"/> <attr name="et_labelTextSize" format="reference|dimension"/> <attr name="et_labelTextColor" format="reference|color"/> <attr name="et_labelTextAppearance" format="reference"/> <attr name="et_labelEllipsize" format="integer"> <enum name="start" value="0x00000001" /> <enum name="middle" value="0x00000002" /> <enum name="end" value="0x00000003" /> <enum name="marquee" value="0x00000004" /> </attr> <attr name="et_labelInAnim" format="reference"/> <attr name="et_labelOutAnim" format="reference"/> <attr name="et_supportMode" format="integer"> <enum name="none" value="0x00000000" /> <enum name="helper" value="0x00000001" /> <enum name="helperWithError" value="0x00000002" /> <enum name="charCounter" value="0x00000003" /> </attr> <attr name="et_supportMaxChars" format="reference|integer"/> <attr name="et_helper" format="reference|string"/> <attr name="et_error" format="reference|string"/> <attr name="et_supportPadding" format="reference|dimension"/> <attr name="et_supportTextSize" format="reference|dimension"/> <attr name="et_supportTextColor" format="reference|color"/> <attr name="et_supportTextErrorColor" format="reference|color"/> <attr name="et_supportTextAppearance" format="reference"/> <attr name="et_supportSingleLine" format="boolean"/> <attr name="et_supportMaxLines" format="reference|integer"/> <attr name="et_supportLines" format="reference|integer"/> <attr name="et_supportEllipsize" format="integer"> <enum name="start" value="0x00000001" /> <enum name="middle" value="0x00000002" /> <enum name="end" value="0x00000003" /> <enum name="marquee" value="0x00000004" /> </attr> <attr name="et_dividerColor" format="reference|color"/> <attr name="et_dividerErrorColor" format="reference|color"/> <attr name="et_dividerHeight" format="reference|dimension"/> <attr name="et_dividerPadding" format="reference|dimension"/> <attr name="et_dividerAnimDuration" format="reference|integer"/> <attr name="et_dividerCompoundPadding" format="boolean"/> <attr name="et_autoCompleteMode" format="integer"> <enum name="none" value="0"/> <enum name="single" value="1"/> <enum name="multi" value="2"/> </attr> </declare-styleable> <declare-styleable name="SnackBar"> <attr name="sb_backgroundColor" format="reference|color"/> <attr name="sb_backgroundCornerRadius" format="reference|dimension"/> <attr name="sb_horizontalPadding" format="reference|dimension"/> <attr name="sb_verticalPadding" format="reference|dimension"/> <attr name="sb_width" format="reference|dimension|integer"> <enum name="match_parent" value="-1" /> <enum name="wrap_content" value="-2" /> </attr> <attr name="sb_minWidth" format="reference|dimension"/> <attr name="sb_maxWidth" format="reference|dimension"/> <attr name="sb_height" format="reference|dimension|integer"> <enum name="match_parent" value="-1" /> <enum name="wrap_content" value="-2" /> </attr> <attr name="sb_minHeight" format="reference|dimension"/> <attr name="sb_maxHeight" format="reference|dimension"/> <attr name="sb_marginLeft" format="reference|dimension"/> <attr name="sb_marginBottom" format="reference|dimension"/> <attr name="sb_textSize" format="reference|dimension"/> <attr name="sb_textColor" format="reference|color"/> <attr name="sb_textAppearance" format="reference"/> <attr name="sb_text" format="reference|string"/> <attr name="sb_singleLine" format="boolean"/> <attr name="sb_maxLines" format="reference|integer"/> <attr name="sb_lines" format="reference|integer"/> <attr name="sb_ellipsize" format="integer"> <enum name="start" value="0x00000001" /> <enum name="middle" value="0x00000002" /> <enum name="end" value="0x00000003" /> <enum name="marquee" value="0x00000004" /> </attr> <attr name="sb_actionTextSize" format="reference|dimension"/> <attr name="sb_actionTextColor" format="reference|color"/> <attr name="sb_actionTextAppearance" format="reference"/> <attr name="sb_actionText" format="reference|string"/> <attr name="sb_actionRipple" format="reference"/> <attr name="sb_duration" format="reference|integer"/> <attr name="sb_inAnimation" format="reference"/> <attr name="sb_outAnimation" format="reference"/> <attr name="sb_removeOnDismiss" format="boolean" /> </declare-styleable> <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="Spinner"> <attr name="android:dropDownWidth" /> <attr name="android:popupBackground" /> <attr name="android:minWidth"/> <attr name="android:minHeight"/> <attr name="spn_labelEnable" format="boolean"/> <attr name="spn_labelPadding" format="reference|dimension"/> <attr name="spn_labelTextSize" format="reference|dimension"/> <attr name="spn_labelTextColor" format="reference|color"/> <attr name="spn_labelTextAppearance" format="reference"/> <attr name="spn_labelEllipsize" format="integer"> <enum name="start" value="0x00000001" /> <enum name="middle" value="0x00000002" /> <enum name="end" value="0x00000003" /> <enum name="marquee" value="0x00000004" /> </attr> <attr name="spn_label" format="string|reference"/> <attr name="spn_popupItemAnimation" format="reference"/> <attr name="spn_popupItemAnimOffset" format="integer"/> <attr name="spn_arrowColor" format="reference|color"/> <attr name="spn_arrowSize" format="reference|dimension"/> <attr name="spn_arrowPadding" format="reference|dimension"/> <attr name="spn_arrowAnimDuration" format="integer"/> <attr name="spn_arrowInterpolator" format="reference"/> <attr name="spn_arrowAnimClockwise" format="boolean"/> <attr name="spn_arrowSwitchMode" format="boolean"/> <attr name="spn_dividerColor" format="reference|color"/> <attr name="spn_dividerHeight" format="reference|dimension"/> <attr name="spn_dividerPadding" format="reference|dimension"/> <attr name="spn_dividerAnimDuration" format="reference|integer"/> </declare-styleable> <declare-styleable name="Dialog"> <attr name="android:layout_width" /> <attr name="android:layout_height" /> <attr name="di_dimAmount" format="float" /> <attr name="di_backgroundColor" format="color|reference" /> <attr name="di_elevation" format="dimension|reference" /> <attr name="di_maxElevation" format="dimension|reference" /> <attr name="di_cornerRadius" format="dimension|reference" /> <attr name="di_titleTextAppearance" format="reference" /> <attr name="di_titleTextColor" format="color|reference" /> <attr name="di_actionBackground" format="reference" /> <attr name="di_actionRipple" format="reference" /> <attr name="di_actionTextAppearance" format="reference" /> <attr name="di_actionTextColor" format="color|reference" /> <attr name="di_positiveActionBackground" format="reference" /> <attr name="di_positiveActionRipple" format="reference" /> <attr name="di_positiveActionTextAppearance" format="reference" /> <attr name="di_positiveActionTextColor" format="color|reference" /> <attr name="di_negativeActionBackground" format="reference" /> <attr name="di_negativeActionRipple" format="reference" /> <attr name="di_negativeActionTextAppearance" format="reference" /> <attr name="di_negativeActionTextColor" format="color|reference" /> <attr name="di_neutralActionBackground" format="reference" /> <attr name="di_neutralActionRipple" format="reference" /> <attr name="di_neutralActionTextAppearance" format="reference" /> <attr name="di_neutralActionTextColor" format="color|reference" /> <attr name="di_dividerColor" format="color|reference" /> <attr name="di_dividerHeight" format="dimension|reference" /> <attr name="di_inAnimation" format="reference" /> <attr name="di_outAnimation" format="reference" /> <attr name="di_cancelable" format="boolean" /> <attr name="di_canceledOnTouchOutside" format="boolean" /> </declare-styleable> <declare-styleable name="SimpleDialog"> <attr name="di_messageTextAppearance" format="reference" /> <attr name="di_messageTextColor" format="color|reference" /> <attr name="di_radioButtonStyle" format="reference" /> <attr name="di_checkBoxStyle" format="reference" /> <attr name="di_itemHeight" format="dimension|reference" /> <attr name="di_itemTextAppearance" format="reference" /> </declare-styleable> <declare-styleable name="TimePicker"> <attr name="tp_backgroundColor" format="color|reference" /> <attr name="tp_selectionColor" format="color|reference" /> <attr name="tp_selectionRadius" format="dimension|reference" /> <attr name="tp_tickSize" format="dimension|reference" /> <attr name="tp_fontFamily" format="string|reference"/> <attr name="tp_textStyle" format="integer"> <enum name="normal" value="0" /> <enum name="bold" value="1" /> <enum name="italic" value="2" /> <enum name="bold_italic" value="3" /> </attr> <attr name="tp_textSize" format="dimension|reference" /> <attr name="tp_textColor" format="color|reference" /> <attr name="tp_textHighlightColor" format="color|reference" /> <attr name="tp_animDuration" format="integer|reference" /> <attr name="tp_inInterpolator" format="reference" /> <attr name="tp_outInterpolator" format="reference" /> <attr name="tp_mode" format="integer"> <enum name="hour" value="0" /> <enum name="minute" value="1" /> </attr> <attr name="tp_24Hour" format="boolean"/> <attr name="tp_hour" format="integer"/> <attr name="tp_minute" format="integer"/> </declare-styleable> <declare-styleable name="TimePickerDialog"> <attr name="tp_headerHeight" format="dimension|reference" /> <attr name="tp_textTimeColor" format="color|reference" /> <attr name="tp_textTimeSize" format="dimension|reference" /> <attr name="tp_am" format="string|reference"/> <attr name="tp_pm" format="string|reference"/> </declare-styleable> <attr name="dp_textHighlightColor" format="color|reference" /> <attr name="dp_textColor" format="color|reference" /> <attr name="dp_selectionColor" format="color|reference" /> <attr name="dp_animDuration" format="integer|reference" /> <attr name="dp_inInterpolator" format="reference" /> <attr name="dp_outInterpolator" format="reference" /> <attr name="dp_yearMin" format="integer"/> <attr name="dp_monthMin" format="integer"/> <attr name="dp_dayMin" format="integer"/> <attr name="dp_yearMax" format="integer"/> <attr name="dp_monthMax" format="integer"/> <attr name="dp_dayMax" format="integer"/> <attr name="dp_year" format="integer"/> <attr name="dp_month" format="integer"/> <attr name="dp_day" format="integer"/> <attr name="dp_fontFamily" format="string|reference"/> <attr name="dp_textStyle" format="integer"> <enum name="normal" value="0" /> <enum name="bold" value="1" /> <enum name="italic" value="2" /> <enum name="bold_italic" value="3" /> </attr> <declare-styleable name="YearPicker"> <attr name="dp_yearMin" /> <attr name="dp_yearMax" /> <attr name="dp_year" /> <attr name="dp_yearTextSize" format="dimension|reference" /> <attr name="dp_yearItemHeight" format="dimension|reference" /> <attr name="dp_textHighlightColor" /> <attr name="dp_textColor" /> <attr name="dp_selectionColor" /> <attr name="dp_animDuration" /> <attr name="dp_inInterpolator" /> <attr name="dp_outInterpolator" /> <attr name="dp_fontFamily" /> <attr name="dp_textStyle" /> </declare-styleable> <declare-styleable name="DatePicker"> <attr name="dp_yearMin" /> <attr name="dp_monthMin" /> <attr name="dp_dayMin" /> <attr name="dp_yearMax" /> <attr name="dp_monthMax" /> <attr name="dp_dayMax" /> <attr name="dp_year" /> <attr name="dp_month" /> <attr name="dp_day" /> <attr name="dp_dayTextSize" format="dimension|reference" /> <attr name="dp_textLabelColor" format="color|reference" /> <attr name="dp_textDisableColor" format="color|reference" /> <attr name="dp_textHighlightColor" /> <attr name="dp_textColor" /> <attr name="dp_selectionColor" /> <attr name="dp_animDuration" /> <attr name="dp_inInterpolator" /> <attr name="dp_outInterpolator" /> <attr name="dp_fontFamily" /> <attr name="dp_textStyle" /> <attr name="android:padding" /> <attr name="android:paddingLeft" /> <attr name="android:paddingTop" /> <attr name="android:paddingRight" /> <attr name="android:paddingBottom" /> </declare-styleable> <declare-styleable name="DatePickerDialog"> <attr name="dp_headerPrimaryHeight" format="dimension|reference" /> <attr name="dp_headerPrimaryColor" format="color|reference" /> <attr name="dp_headerSecondaryHeight" format="dimension|reference" /> <attr name="dp_headerSecondaryColor" format="color|reference" /> <attr name="dp_headerPrimaryTextSize" format="dimension|reference" /> <attr name="dp_headerSecondaryTextSize" format="dimension|reference" /> <attr name="dp_textHeaderColor" format="color|reference" /> </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> </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="Material.Widget.Switch"> <item name="sw_trackSize">14dp</item> <item name="sw_trackCap">round</item> <item name="sw_thumbRadius">10dp</item> <item name="sw_thumbElevation">2dp</item> <item name="sw_animDuration">@android:integer/config_shortAnimTime</item> <item name="sw_interpolator">@android:anim/decelerate_interpolator</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> <!-- Switch Style --> <style name="RadioButtonDrawable" parent="Material.Drawable.RadioButton"/> <style name="CheckBoxDrawable" parent="Material.Drawable.CheckBox"/> <!-- SnackBar Style --> <style name="Material.Drawable.RadioButton"> <item name="rbd_width">32dp</item> <item name="rbd_height">32dp</item> <item name="rbd_strokeSize">2dp</item> <item name="rbd_radius">10dp</item> <item name="rbd_innerRadius">5dp</item> <item name="rbd_animDuration">@android:integer/config_mediumAnimTime</item> </style> <style name="Material.Drawable.CheckBox"> <item name="cbd_width">32dp</item> <item name="cbd_height">32dp</item> <item name="cbd_boxSize">20dp</item> <item name="cbd_cornerRadius">2dp</item> <item name="cbd_strokeSize">2dp</item> <item name="cbd_animDuration">@android:integer/config_mediumAnimTime</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="Checkbox"/> <com.rey.material.widget.CheckBox style="@style/CheckBoxDrawable" android:id="@+id/switches_cb1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Checkbox 1" android:textSize="14sp" android:checked="true" android:gravity="center_vertical"/> <com.rey.material.widget.CheckBox style="@style/CheckBoxDrawable" android:id="@+id/switches_cb2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Checkbox 2" android:textSize="14sp" android:checked="false" android:gravity="center_vertical"/> <com.rey.material.widget.CheckBox style="@style/CheckBoxDrawable" android:id="@+id/switches_cb3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Checkbox 3" android:textSize="14sp" android:checked="false" android:gravity="center_vertical"/> <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="Radio Button"/> <com.rey.material.widget.RadioButton style="@style/RadioButtonDrawable" android:id="@+id/switches_rb1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Radio Button 1" android:textSize="14sp" android:checked="true" android:gravity="center_vertical"/> <com.rey.material.widget.RadioButton style="@style/RadioButtonDrawable" android:id="@+id/switches_rb2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Radio Button 2" android:textSize="14sp" android:checked="false" android:gravity="center_vertical"/> <com.rey.material.widget.RadioButton style="@style/RadioButtonDrawable" android:id="@+id/switches_rb3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Radio Button 3" android:textSize="14sp" android:checked="false" android:gravity="center_vertical"/> <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="Switch"/> <com.rey.material.widget.Switch style="@style/Material.Widget.Switch" android:id="@+id/switches_sw1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:checked="false" android:padding="16dp"/> <com.rey.material.widget.Switch style="@style/Material.Widget.Switch" android:id="@+id/switches_sw2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:checked="true" android:layout_marginTop="16dp"/> </LinearLayout> </ScrollView>
MainActivity.java
package com.example.switchesformaterialdesign; import android.app.Activity; import android.os.Bundle; import android.widget.CompoundButton; import com.rey.material.widget.RadioButton; public class MainActivity extends Activity { RadioButton rb1; RadioButton rb2; RadioButton rb3; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); rb1 = (RadioButton)findViewById(R.id.switches_rb1); rb2 = (RadioButton)findViewById(R.id.switches_rb2); rb3 = (RadioButton)findViewById(R.id.switches_rb3); CompoundButton.OnCheckedChangeListener listener = new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if(isChecked){ rb1.setChecked(rb1 == buttonView); rb2.setChecked(rb2 == buttonView); rb3.setChecked(rb3 == buttonView); } } }; rb1.setOnCheckedChangeListener(listener); rb2.setOnCheckedChangeListener(listener); rb3.setOnCheckedChangeListener(listener); } }
CheckBoxDrawable.java
package com.rey.material.drawable; import android.content.Context; import android.content.res.ColorStateList; 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.Rect; import android.graphics.RectF; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.os.SystemClock; import android.util.AttributeSet; import com.example.switchesformaterialdesign.R; import com.rey.material.util.ColorUtil; import com.rey.material.util.ThemeUtil; import com.rey.material.util.ViewUtil; public class CheckBoxDrawable extends Drawable implements Animatable { private boolean mRunning = false; private Paint mPaint; private long mStartTime; private float mAnimProgress; private int mAnimDuration; private int mStrokeSize; private int mWidth; private int mHeight; private int mCornerRadius; private int mBoxSize; private int mTickColor; private int mPrevColor; private int mCurColor; private ColorStateList mStrokeColor; private RectF mBoxRect; private Path mTickPath; private float mTickPathProgress = -1f; private boolean mChecked = false; private boolean mInEditMode = false; private boolean mAnimEnable = true; private static final float[] TICK_DATA = new float[]{0f, 0.473f, 0.367f, 0.839f, 1f, 0.207f}; private static final float FILL_TIME = 0.4f; private CheckBoxDrawable(int width, int height, int boxSize, int cornerRadius, int strokeSize, ColorStateList strokeColor, int tickColor, int animDuration){ mWidth = width; mHeight = height; mBoxSize = boxSize; mCornerRadius = cornerRadius; mStrokeSize = strokeSize; mStrokeColor = strokeColor; mTickColor = tickColor; mAnimDuration = animDuration; mPaint = new Paint(); mPaint.setAntiAlias(true); mBoxRect = new RectF(); mTickPath = new Path(); } public void setInEditMode(boolean b){ mInEditMode = b; } public void setAnimEnable(boolean b){ mAnimEnable = b; } public boolean isAnimEnable(){ return mAnimEnable; } @Override public int getIntrinsicWidth() { return mWidth; } @Override public int getIntrinsicHeight() { return mHeight; } @Override public int getMinimumWidth() { return mWidth; } @Override public int getMinimumHeight() { return mHeight; } @Override public boolean isStateful() { return true; } @Override protected void onBoundsChange(Rect bounds) { mBoxRect.set(bounds.exactCenterX() - mBoxSize / 2, bounds.exactCenterY() - mBoxSize / 2, bounds.exactCenterX() + mBoxSize / 2, bounds.exactCenterY() + mBoxSize / 2); } @Override public void draw(Canvas canvas) { if(mChecked) drawChecked(canvas); else drawUnchecked(canvas); } private Path getTickPath(Path path, float x, float y, float size, float progress, boolean in){ if(mTickPathProgress == progress) return path; mTickPathProgress = progress; float x1 = x + size * TICK_DATA[0]; float y1 = y + size * TICK_DATA[1]; float x2 = x + size * TICK_DATA[2]; float y2 = y + size * TICK_DATA[3]; float x3 = x + size * TICK_DATA[4]; float y3 = y + size * TICK_DATA[5]; float d1 = (float)Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); float d2 = (float)Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); float midProgress = d1 / (d1 + d2); path.reset(); if(in){ path.moveTo(x1, y1); if(progress < midProgress){ progress = progress / midProgress; path.lineTo(x1 * (1 - progress) + x2 * progress, y1 * (1 - progress) + y2 * progress); } else{ progress = (progress - midProgress) / (1f - midProgress); path.lineTo(x2, y2); path.lineTo(x2 * (1 - progress) + x3 * progress, y2 * (1 - progress) + y3 * progress); } } else{ path.moveTo(x3, y3); if(progress < midProgress){ progress = progress / midProgress; path.lineTo(x2, y2); path.lineTo(x1 * (1 - progress) + x2 * progress, y1 * (1 - progress) + y2 * progress); } else{ progress = (progress - midProgress) / (1f - midProgress); path.lineTo(x2 * (1 - progress) + x3 * progress, y2 * (1 - progress) + y3 * progress); } } return path; } private void drawChecked(Canvas canvas){ float size = mBoxSize - mStrokeSize * 2; float x = mBoxRect.left + mStrokeSize; float y = mBoxRect.top + mStrokeSize; if(isRunning()){ if(mAnimProgress < FILL_TIME){ float progress = mAnimProgress / FILL_TIME; float fillWidth = (mBoxSize - mStrokeSize) / 2f * progress; float padding = mStrokeSize / 2f + fillWidth / 2f - 0.5f; mPaint.setColor(ColorUtil.getMiddleColor(mPrevColor, mCurColor, progress)); mPaint.setStrokeWidth(fillWidth); mPaint.setStyle(Paint.Style.STROKE); canvas.drawRect(mBoxRect.left + padding, mBoxRect.top + padding, mBoxRect.right - padding, mBoxRect.bottom - padding, mPaint); mPaint.setStrokeWidth(mStrokeSize); canvas.drawRoundRect(mBoxRect, mCornerRadius, mCornerRadius, mPaint); } else{ float progress = (mAnimProgress - FILL_TIME) / (1f - FILL_TIME); mPaint.setColor(mCurColor); mPaint.setStrokeWidth(mStrokeSize); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); canvas.drawRoundRect(mBoxRect, mCornerRadius, mCornerRadius, mPaint); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeJoin(Paint.Join.MITER); mPaint.setStrokeCap(Paint.Cap.BUTT); mPaint.setColor(mTickColor); canvas.drawPath(getTickPath(mTickPath, x, y, size, progress, true), mPaint); } } else{ mPaint.setColor(mCurColor); mPaint.setStrokeWidth(mStrokeSize); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); canvas.drawRoundRect(mBoxRect, mCornerRadius, mCornerRadius, mPaint); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeJoin(Paint.Join.MITER); mPaint.setStrokeCap(Paint.Cap.BUTT); mPaint.setColor(mTickColor); canvas.drawPath(getTickPath(mTickPath, x, y, size, 1f, true), mPaint); } } private void drawUnchecked(Canvas canvas){ if(isRunning()){ if(mAnimProgress < 1f - FILL_TIME){ float size = mBoxSize - mStrokeSize * 2; float x = mBoxRect.left + mStrokeSize; float y = mBoxRect.top + mStrokeSize; float progress = mAnimProgress / (1f -FILL_TIME); mPaint.setColor(mPrevColor); mPaint.setStrokeWidth(mStrokeSize); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); canvas.drawRoundRect(mBoxRect, mCornerRadius, mCornerRadius, mPaint); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeJoin(Paint.Join.MITER); mPaint.setStrokeCap(Paint.Cap.BUTT); mPaint.setColor(mTickColor); canvas.drawPath(getTickPath(mTickPath, x, y, size, progress, false), mPaint); } else{ float progress = (mAnimProgress + FILL_TIME - 1f) / FILL_TIME; float fillWidth = (mBoxSize - mStrokeSize) / 2f * (1f - progress); float padding = mStrokeSize / 2f + fillWidth / 2f - 0.5f; mPaint.setColor(ColorUtil.getMiddleColor(mPrevColor, mCurColor, progress)); mPaint.setStrokeWidth(fillWidth); mPaint.setStyle(Paint.Style.STROKE); canvas.drawRect(mBoxRect.left + padding, mBoxRect.top + padding, mBoxRect.right - padding, mBoxRect.bottom - padding, mPaint); mPaint.setStrokeWidth(mStrokeSize); canvas.drawRoundRect(mBoxRect, mCornerRadius, mCornerRadius, mPaint); } } else{ mPaint.setColor(mCurColor); mPaint.setStrokeWidth(mStrokeSize); mPaint.setStyle(Paint.Style.STROKE); canvas.drawRoundRect(mBoxRect, mCornerRadius, mCornerRadius, mPaint); } } @Override protected boolean onStateChange(int[] state) { boolean checked = ViewUtil.hasState(state, android.R.attr.state_checked); int color = mStrokeColor.getColorForState(state, mCurColor); boolean needRedraw = false; if(mChecked != checked){ mChecked = checked; needRedraw = true; if(!mInEditMode && mAnimEnable) start(); } if(mCurColor != color){ mPrevColor = isRunning() ? mCurColor : color; mCurColor = color; needRedraw = true; } else if(!isRunning()) mPrevColor = color; return needRedraw; } @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; } //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() { 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(); mAnimProgress = Math.min(1f, (float)(curTime - mStartTime) / mAnimDuration); if(mAnimProgress == 1f) mRunning = false; if(isRunning()) scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); invalidateSelf(); } public static class Builder{ private int mAnimDuration = 400; private int mStrokeSize = 4; private int mWidth = 64; private int mHeight = 64; private ColorStateList mStrokeColor; private int mCornerRadius = 8; private int mBoxSize = 32; private int mTickColor = 0xFFFFFFFF; 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.CheckBoxDrawable, defStyleAttr, defStyleRes); width(a.getDimensionPixelSize(R.styleable.CheckBoxDrawable_cbd_width, ThemeUtil.dpToPx(context, 32))); height(a.getDimensionPixelSize(R.styleable.CheckBoxDrawable_cbd_height, ThemeUtil.dpToPx(context, 32))); boxSize(a.getDimensionPixelSize(R.styleable.CheckBoxDrawable_cbd_boxSize, ThemeUtil.dpToPx(context, 18))); cornerRadius(a.getDimensionPixelSize(R.styleable.CheckBoxDrawable_cbd_cornerRadius, ThemeUtil.dpToPx(context, 2))); strokeSize(a.getDimensionPixelSize(R.styleable.CheckBoxDrawable_cbd_strokeSize, ThemeUtil.dpToPx(context, 2))); strokeColor(a.getColorStateList(R.styleable.CheckBoxDrawable_cbd_strokeColor)); tickColor(a.getColor(R.styleable.CheckBoxDrawable_cbd_tickColor, 0xFFFFFFFF)); animDuration(a.getInt(R.styleable.CheckBoxDrawable_cbd_animDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime))); a.recycle(); if(mStrokeColor == null){ int[][] states = new int[][]{ new int[]{-android.R.attr.state_checked}, new int[]{android.R.attr.state_checked}, }; int[] colors = new int[]{ ThemeUtil.colorControlNormal(context, 0xFF000000), ThemeUtil.colorControlActivated(context, 0xFF000000), }; strokeColor(new ColorStateList(states, colors)); } } public CheckBoxDrawable build(){ if(mStrokeColor == null) mStrokeColor = ColorStateList.valueOf(0xFF000000); return new CheckBoxDrawable(mWidth, mHeight, mBoxSize, mCornerRadius, mStrokeSize, mStrokeColor, mTickColor, mAnimDuration); } public Builder width(int width){ mWidth = width; return this; } public Builder height(int height){ mHeight = height; return this; } public Builder strokeSize(int size){ mStrokeSize = size; return this; } public Builder strokeColor(int color){ mStrokeColor = ColorStateList.valueOf(color); return this; } public Builder strokeColor(ColorStateList color){ mStrokeColor = color; return this; } public Builder tickColor(int color){ mTickColor = color; return this; } public Builder cornerRadius(int radius){ mCornerRadius = radius; return this; } public Builder boxSize(int size){ mBoxSize = size; return this; } public Builder animDuration(int duration){ mAnimDuration = duration; return this; } } }
RadioButtonDrawable.java
package com.rey.material.drawable; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.os.SystemClock; import android.util.AttributeSet; import com.example.switchesformaterialdesign.R; import com.rey.material.util.ColorUtil; import com.rey.material.util.ThemeUtil; import com.rey.material.util.ViewUtil; public class RadioButtonDrawable extends Drawable implements Animatable { private boolean mRunning = false; private Paint mPaint; private long mStartTime; private float mAnimProgress; private int mAnimDuration; private int mStrokeSize; private int mWidth; private int mHeight; private int mRadius; private int mInnerRadius; private int mPrevColor; private int mCurColor; private ColorStateList mStrokeColor; private boolean mChecked = false; private boolean mInEditMode = false; private boolean mAnimEnable = true; private RadioButtonDrawable(int width, int height, int strokeSize, ColorStateList strokeColor, int radius, int innerRadius, int animDuration){ mAnimDuration = animDuration; mStrokeSize = strokeSize; mWidth = width; mHeight = height; mRadius = radius; mInnerRadius = innerRadius; mStrokeColor = strokeColor; mPaint = new Paint(); mPaint.setAntiAlias(true); } public void setInEditMode(boolean b){ mInEditMode = b; } public void setAnimEnable(boolean b){ mAnimEnable = b; } public boolean isAnimEnable(){ return mAnimEnable; } @Override public int getIntrinsicWidth() { return mWidth; } @Override public int getIntrinsicHeight() { return mHeight; } @Override public int getMinimumWidth() { return mWidth; } @Override public int getMinimumHeight() { return mHeight; } @Override public boolean isStateful() { return true; } @Override public void draw(Canvas canvas) { if(mChecked) drawChecked(canvas); else drawUnchecked(canvas); } private void drawChecked(Canvas canvas){ float cx = getBounds().exactCenterX(); float cy = getBounds().exactCenterY(); if(isRunning()){ float halfStrokeSize = mStrokeSize / 2f; float inTime = (mRadius - halfStrokeSize) / (mRadius - halfStrokeSize + mRadius - mStrokeSize - mInnerRadius); if(mAnimProgress < inTime){ float inProgress = mAnimProgress / inTime; float outerRadius = mRadius + halfStrokeSize * (1f - inProgress); float innerRadius = (mRadius - halfStrokeSize) * (1f - inProgress); mPaint.setColor(ColorUtil.getMiddleColor(mPrevColor, mCurColor, inProgress)); mPaint.setStrokeWidth(outerRadius - innerRadius); mPaint.setStyle(Paint.Style.STROKE); canvas.drawCircle(cx, cy, (outerRadius + innerRadius) / 2, mPaint); } else{ float outProgress = (mAnimProgress - inTime) / (1f - inTime); float innerRadius = (mRadius - mStrokeSize) * (1 - outProgress) + mInnerRadius * outProgress; mPaint.setColor(mCurColor); mPaint.setStyle(Paint.Style.FILL); canvas.drawCircle(cx, cy, innerRadius, mPaint); float outerRadius = mRadius + halfStrokeSize * outProgress; mPaint.setStrokeWidth(mStrokeSize); mPaint.setStyle(Paint.Style.STROKE); canvas.drawCircle(cx, cy, outerRadius - halfStrokeSize, mPaint); } } else{ mPaint.setColor(mCurColor); mPaint.setStrokeWidth(mStrokeSize); mPaint.setStyle(Paint.Style.STROKE); canvas.drawCircle(cx, cy, mRadius, mPaint); mPaint.setStyle(Paint.Style.FILL); canvas.drawCircle(cx, cy, mInnerRadius, mPaint); } } private void drawUnchecked(Canvas canvas){ float cx = getBounds().exactCenterX(); float cy = getBounds().exactCenterY(); if(isRunning()){ float halfStrokeSize = mStrokeSize / 2f; float inTime = (mRadius - mStrokeSize - mInnerRadius) / (mRadius - halfStrokeSize + mRadius - mStrokeSize - mInnerRadius); if(mAnimProgress < inTime){ float inProgress = mAnimProgress / inTime; float innerRadius = (mRadius - mStrokeSize) * inProgress + mInnerRadius * (1f - inProgress); mPaint.setColor(ColorUtil.getMiddleColor(mPrevColor, mCurColor, inProgress)); mPaint.setStyle(Paint.Style.FILL); canvas.drawCircle(cx, cy, innerRadius, mPaint); float outerRadius = mRadius + halfStrokeSize * (1f - inProgress); mPaint.setStrokeWidth(mStrokeSize); mPaint.setStyle(Paint.Style.STROKE); canvas.drawCircle(cx, cy, outerRadius - halfStrokeSize, mPaint); } else{ float outProgress = (mAnimProgress - inTime) / (1f - inTime); float outerRadius = mRadius + halfStrokeSize * outProgress; float innerRadius = (mRadius - halfStrokeSize) * outProgress; mPaint.setColor(mCurColor); mPaint.setStrokeWidth(outerRadius - innerRadius); mPaint.setStyle(Paint.Style.STROKE); canvas.drawCircle(cx, cy, (outerRadius + innerRadius) / 2, mPaint); } } else{ mPaint.setColor(mCurColor); mPaint.setStrokeWidth(mStrokeSize); mPaint.setStyle(Paint.Style.STROKE); canvas.drawCircle(cx, cy, mRadius, mPaint); } } @Override protected boolean onStateChange(int[] state) { boolean checked = ViewUtil.hasState(state, android.R.attr.state_checked); int color = mStrokeColor.getColorForState(state, mCurColor); boolean needRedraw = false; if(mChecked != checked){ mChecked = checked; needRedraw = true; if(!mInEditMode && mAnimEnable) start(); } if(mCurColor != color){ mPrevColor = isRunning() ? mCurColor : color; mCurColor = color; needRedraw = true; } else if(!isRunning()) mPrevColor = color; return needRedraw; } @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; } //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() { 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(); mAnimProgress = Math.min(1f, (float)(curTime - mStartTime) / mAnimDuration); if(mAnimProgress == 1f) mRunning = false; if(isRunning()) scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); invalidateSelf(); } public static class Builder{ private int mAnimDuration = 400; private int mStrokeSize = 4; private int mWidth = 64; private int mHeight = 64; private int mRadius = 18; private int mInnerRadius = 10; private ColorStateList mStrokeColor; 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.RadioButtonDrawable, defStyleAttr, defStyleRes); width(a.getDimensionPixelSize(R.styleable.RadioButtonDrawable_rbd_width, ThemeUtil.dpToPx(context, 32))); height(a.getDimensionPixelSize(R.styleable.RadioButtonDrawable_rbd_height, ThemeUtil.dpToPx(context, 32))); strokeSize(a.getDimensionPixelSize(R.styleable.RadioButtonDrawable_rbd_strokeSize, ThemeUtil.dpToPx(context, 2))); radius(a.getDimensionPixelSize(R.styleable.RadioButtonDrawable_rbd_radius, ThemeUtil.dpToPx(context, 10))); innerRadius(a.getDimensionPixelSize(R.styleable.RadioButtonDrawable_rbd_innerRadius, ThemeUtil.dpToPx(context, 5))); strokeColor(a.getColorStateList(R.styleable.RadioButtonDrawable_rbd_strokeColor)); animDuration(a.getInt(R.styleable.RadioButtonDrawable_rbd_animDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime))); a.recycle(); if(mStrokeColor == null){ int[][] states = new int[][]{ new int[]{-android.R.attr.state_checked}, new int[]{android.R.attr.state_checked}, }; int[] colors = new int[]{ ThemeUtil.colorControlNormal(context, 0xFF000000), ThemeUtil.colorControlActivated(context, 0xFF000000), }; strokeColor(new ColorStateList(states, colors)); } } public RadioButtonDrawable build(){ if(mStrokeColor == null) mStrokeColor = ColorStateList.valueOf(0xFF000000); return new RadioButtonDrawable(mWidth, mHeight, mStrokeSize, mStrokeColor, mRadius, mInnerRadius, mAnimDuration); } public Builder width(int width){ mWidth = width; return this; } public Builder height(int height){ mHeight = height; return this; } public Builder strokeSize(int size){ mStrokeSize = size; return this; } public Builder strokeColor(int color){ mStrokeColor = ColorStateList.valueOf(color); return this; } public Builder strokeColor(ColorStateList color){ mStrokeColor = color; return this; } public Builder radius(int radius){ mRadius = radius; return this; } public Builder innerRadius(int radius){ mInnerRadius = radius; return this; } public Builder animDuration(int duration){ mAnimDuration = duration; 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.switchesformaterialdesign.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.switchesformaterialdesign.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.switchesformaterialdesign.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; } }
TypefaceUtil.java
package com.rey.material.util; import android.content.Context; import android.graphics.Typeface; import java.util.HashMap; /** * Created by Rey on 12/23/2014. */ public class TypefaceUtil { private static final HashMap<String, Typeface> sCachedFonts = new HashMap<String, Typeface>(); private static final String PREFIX_ASSET = "asset:"; private TypefaceUtil() { } /** * @param familyName if start with 'asset:' prefix, then load font from asset folder. * @return */ public static Typeface load(Context context, String familyName, int style) { if(familyName != null && familyName.startsWith(PREFIX_ASSET)) synchronized (sCachedFonts) { try { if (!sCachedFonts.containsKey(familyName)) { final Typeface typeface = Typeface.createFromAsset(context.getAssets(), familyName); sCachedFonts.put(familyName, typeface); return typeface; } } catch (Exception e) { return Typeface.DEFAULT; } return sCachedFonts.get(familyName); } return Typeface.create(familyName, style); } }
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; } }
CheckBox.java
package com.rey.material.widget; import com.rey.material.drawable.CheckBoxDrawable; import android.content.Context; import android.util.AttributeSet; public class CheckBox extends CompoundButton { public CheckBox(Context context) { super(context); init(context, null, 0, 0); } public CheckBox(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public CheckBox(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public CheckBox(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){ CheckBoxDrawable drawable = new CheckBoxDrawable.Builder(context, attrs, defStyleAttr, defStyleRes).build(); drawable.setInEditMode(isInEditMode()); drawable.setAnimEnable(false); setButtonDrawable(drawable); drawable.setAnimEnable(true); } public void setCheckedImmediately(boolean checked){ if(mButtonDrawable instanceof CheckBoxDrawable){ CheckBoxDrawable drawable = (CheckBoxDrawable)mButtonDrawable; drawable.setAnimEnable(false); setChecked(checked); drawable.setAnimEnable(true); } else setChecked(checked); } }
CompoundButton.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.support.annotation.NonNull; import android.util.AttributeSet; import android.view.MotionEvent; import com.rey.material.drawable.RippleDrawable; public class CompoundButton extends android.widget.CompoundButton { private RippleManager mRippleManager = new RippleManager(); protected Drawable mButtonDrawable; public CompoundButton(Context context) { super(context); init(context, null, 0, 0); } public CompoundButton(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public CompoundButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public CompoundButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, defStyleRes); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){ //a fix to reset paddingLeft attribute if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1){ TypedArray a = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.padding, android.R.attr.paddingLeft}, defStyleAttr, defStyleRes); if(!a.hasValue(0) && !a.hasValue(1)) setPadding(0, getPaddingTop(), getPaddingRight(), getPaddingBottom()); a.recycle(); } setClickable(true); 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; } @Override public void setButtonDrawable(Drawable d) { mButtonDrawable = d; super.setButtonDrawable(d); } @Override public int getCompoundPaddingLeft() { int padding = super.getCompoundPaddingLeft(); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) return padding; if (mButtonDrawable != null) padding += mButtonDrawable.getIntrinsicWidth(); return padding; } }
RadioButton.java
package com.rey.material.widget; import android.content.Context; import android.util.AttributeSet; import com.rey.material.drawable.CheckBoxDrawable; import com.rey.material.drawable.RadioButtonDrawable; public class RadioButton extends CompoundButton { public RadioButton(Context context) { super(context); init(context, null, 0, 0); } public RadioButton(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public RadioButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public RadioButton(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){ RadioButtonDrawable drawable = new RadioButtonDrawable.Builder(context, attrs, defStyleAttr, defStyleRes).build(); drawable.setInEditMode(isInEditMode()); drawable.setAnimEnable(false); setButtonDrawable(drawable); drawable.setAnimEnable(true); } @Override public void toggle() { // we override to prevent toggle when the radio is already // checked (as opposed to check boxes widgets) if (!isChecked()) { super.toggle(); } } public void setCheckedImmediately(boolean checked){ if(mButtonDrawable instanceof RadioButtonDrawable){ RadioButtonDrawable drawable = (RadioButtonDrawable)mButtonDrawable; drawable.setAnimEnable(false); setChecked(checked); drawable.setAnimEnable(true); } else setChecked(checked); } }
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.switchesformaterialdesign.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)); } } }
Switch.java
package com.rey.material.widget; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RadialGradient; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.widget.Checkable; import com.example.switchesformaterialdesign.R; import com.rey.material.drawable.RippleDrawable; import com.rey.material.util.ColorUtil; import com.rey.material.util.ThemeUtil; import com.rey.material.util.ViewUtil; public class Switch extends View implements Checkable { private RippleManager mRippleManager = new RippleManager(); private boolean mRunning = false; private Paint mPaint; private RectF mDrawRect; private RectF mTempRect; private Path mTrackPath; private int mTrackSize; private ColorStateList mTrackColors; private Paint.Cap mTrackCap; private int mThumbRadius; private ColorStateList mThumbColors; private float mThumbPosition; private int mMaxAnimDuration; private Interpolator mInterpolator; private int mGravity = Gravity.CENTER_VERTICAL; private boolean mChecked = false; private float mMemoX; private float mStartX; private float mFlingVelocity; private long mStartTime; private int mAnimDuration; private float mStartPosition; private int[] mTempStates = new int[2]; private int mShadowSize; private int mShadowOffset; private Path mShadowPath; private Paint mShadowPaint; private static final int COLOR_SHADOW_START = 0x4C000000; private static final int COLOR_SHADOW_END = 0x00000000; public interface OnCheckedChangeListener{ public void onCheckedChanged(Switch view, boolean checked); } private OnCheckedChangeListener mOnCheckedChangeListener; public Switch(Context context) { super(context); init(context, null, 0, 0); } public Switch(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public Switch(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public Switch(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){ mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mDrawRect = new RectF(); mTempRect = new RectF(); mTrackPath = new Path(); mFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity(); 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); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Switch, defStyleAttr, defStyleRes); mTrackSize = a.getDimensionPixelSize(R.styleable.Switch_sw_trackSize, ThemeUtil.dpToPx(context, 2)); mTrackColors = a.getColorStateList(R.styleable.Switch_sw_trackColor); int cap = a.getInteger(R.styleable.Switch_sw_trackCap, 0); if(cap == 0) mTrackCap = Paint.Cap.BUTT; else if(cap == 1) mTrackCap = Paint.Cap.ROUND; else mTrackCap = Paint.Cap.SQUARE; mThumbColors = a.getColorStateList(R.styleable.Switch_sw_thumbColor); mThumbRadius = a.getDimensionPixelSize(R.styleable.Switch_sw_thumbRadius, ThemeUtil.dpToPx(context, 8)); mShadowSize = a.getDimensionPixelSize(R.styleable.Switch_sw_thumbElevation, ThemeUtil.dpToPx(context, 2)); mShadowOffset = mShadowSize / 2; mMaxAnimDuration = a.getInt(R.styleable.Switch_sw_animDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime)); mGravity = a.getInt(R.styleable.Switch_android_gravity, Gravity.CENTER_VERTICAL); mChecked = a.getBoolean(R.styleable.Switch_android_checked, false); mThumbPosition = mChecked ? 1f : 0f; int resId = a.getResourceId(R.styleable.Switch_sw_interpolator, 0); mInterpolator = resId != 0 ? AnimationUtils.loadInterpolator(context, resId) : new DecelerateInterpolator(); a.recycle(); if(mTrackColors == null){ int[][] states = new int[][]{ new int[]{-android.R.attr.state_checked}, new int[]{android.R.attr.state_checked}, }; int[] colors = new int[]{ ColorUtil.getColor(ThemeUtil.colorControlNormal(context, 0xFF000000), 0.5f), ColorUtil.getColor(ThemeUtil.colorControlActivated(context, 0xFF000000), 0.5f), }; mTrackColors = new ColorStateList(states, colors); } if(mThumbColors == null){ int[][] states = new int[][]{ new int[]{-android.R.attr.state_checked}, new int[]{android.R.attr.state_checked}, }; int[] colors = new int[]{ 0xFAFAFA, ThemeUtil.colorControlActivated(context, 0xFF000000), }; mThumbColors = new ColorStateList(states, colors); } mPaint.setStrokeCap(mTrackCap); buildShadow(); } @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); } } public void setOnCheckedChangeListener(OnCheckedChangeListener listener){ mOnCheckedChangeListener = listener; } @Override public void setChecked(boolean checked) { if(mChecked != checked) { mChecked = checked; if(mOnCheckedChangeListener != null) mOnCheckedChangeListener.onCheckedChanged(this, mChecked); } float desPos = mChecked ? 1f : 0f; if(mThumbPosition != desPos) startAnimation(); } @Override public boolean isChecked() { return mChecked; } @Override public void toggle() { if(isEnabled()) setChecked(!mChecked); } @Override public boolean onTouchEvent(@NonNull MotionEvent event) { super.onTouchEvent(event); mRippleManager.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mMemoX = event.getX(); mStartX = mMemoX; mStartTime = SystemClock.uptimeMillis(); break; case MotionEvent.ACTION_MOVE: float offset = (event.getX() - mMemoX) / (mDrawRect.width() - mThumbRadius * 2); mThumbPosition = Math.min(1f, Math.max(0f, mThumbPosition + offset)); mMemoX = event.getX(); invalidate(); break; case MotionEvent.ACTION_UP: float velocity = (event.getX() - mStartX) / (SystemClock.uptimeMillis() - mStartTime) * 1000; if(Math.abs(velocity) >= mFlingVelocity) setChecked(velocity > 0); else if((!mChecked && mThumbPosition < 0.1f) || (mChecked && mThumbPosition > 0.9f)) toggle(); else setChecked(mThumbPosition > 0.5f); break; case MotionEvent.ACTION_CANCEL: setChecked(mThumbPosition > 0.5f); break; } return true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSize = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); switch (widthMode) { case MeasureSpec.UNSPECIFIED: widthSize = getSuggestedMinimumWidth(); break; case MeasureSpec.AT_MOST: widthSize = Math.min(widthSize, getSuggestedMinimumWidth()); break; } switch (heightMode) { case MeasureSpec.UNSPECIFIED: heightSize = getSuggestedMinimumHeight(); break; case MeasureSpec.AT_MOST: heightSize = Math.min(heightSize, getSuggestedMinimumHeight()); break; } setMeasuredDimension(widthSize, heightSize); } @Override public int getSuggestedMinimumWidth() { return mThumbRadius * 4 + Math.max(mShadowSize, getPaddingLeft()) + Math.max(mShadowSize, getPaddingRight()); } @Override public int getSuggestedMinimumHeight() { return mThumbRadius * 2 + Math.max(mShadowSize - mShadowOffset, getPaddingTop()) + Math.max(mShadowSize + mShadowOffset, getPaddingBottom()); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { mDrawRect.left = Math.max(mShadowSize, getPaddingLeft()); mDrawRect.right = w - Math.max(mShadowSize, getPaddingRight()); int height = mThumbRadius * 2; int align = mGravity & Gravity.VERTICAL_GRAVITY_MASK; switch (align) { case Gravity.TOP: mDrawRect.top = Math.max(mShadowSize - mShadowOffset, getPaddingTop()); mDrawRect.bottom = mDrawRect.top + height; break; case Gravity.BOTTOM: mDrawRect.bottom = h - Math.max(mShadowSize + mShadowOffset, getPaddingBottom()); mDrawRect.top = mDrawRect.bottom - height; break; default: mDrawRect.top = (h - height) / 2f; mDrawRect.bottom = mDrawRect.top + height; break; } } private int getTrackColor(boolean checked){ mTempStates[0] = isEnabled() ? android.R.attr.state_enabled : -android.R.attr.state_enabled; mTempStates[1] = checked ? android.R.attr.state_checked : -android.R.attr.state_checked; return mTrackColors.getColorForState(mTempStates, 0); } private int getThumbColor(boolean checked){ mTempStates[0] = isEnabled() ? android.R.attr.state_enabled : -android.R.attr.state_enabled; mTempStates[1] = checked ? android.R.attr.state_checked : -android.R.attr.state_checked; return mThumbColors.getColorForState(mTempStates, 0); } 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)mThumbRadius / (mThumbRadius + mShadowSize + mShadowOffset); mShadowPaint.setShader(new RadialGradient(0, 0, mThumbRadius + 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 = mThumbRadius + mShadowSize; mTempRect.set(-radius, -radius, radius, radius); mShadowPath.addOval(mTempRect, Path.Direction.CW); radius = mThumbRadius - 1; mTempRect.set(-radius, -radius - mShadowOffset, radius, radius - mShadowOffset); mShadowPath.addOval(mTempRect, Path.Direction.CW); } private void getTrackPath(float x, float y, float radius){ float halfStroke = mTrackSize / 2f; mTrackPath.reset(); if(mTrackCap != Paint.Cap.ROUND){ mTempRect.set(x - radius + 1f, y - radius + 1f, x + radius - 1f, y + radius - 1f); float angle = (float)(Math.asin(halfStroke / (radius - 1f)) / Math.PI * 180); if(x - radius > mDrawRect.left){ mTrackPath.moveTo(mDrawRect.left, y - halfStroke); mTrackPath.arcTo(mTempRect, 180 + angle, -angle * 2); mTrackPath.lineTo(mDrawRect.left, y + halfStroke); mTrackPath.close(); } if(x + radius < mDrawRect.right){ mTrackPath.moveTo(mDrawRect.right, y - halfStroke); mTrackPath.arcTo(mTempRect, -angle, angle * 2); mTrackPath.lineTo(mDrawRect.right, y + halfStroke); mTrackPath.close(); } } else{ float angle = (float)(Math.asin(halfStroke / (radius - 1f)) / Math.PI * 180); if(x - radius > mDrawRect.left){ float angle2 = (float)(Math.acos(Math.max(0f, (mDrawRect.left + halfStroke - x + radius) / halfStroke)) / Math.PI * 180); mTempRect.set(mDrawRect.left, y - halfStroke, mDrawRect.left + mTrackSize, y + halfStroke); mTrackPath.arcTo(mTempRect, 180 - angle2, angle2 * 2); mTempRect.set(x - radius + 1f, y - radius + 1f, x + radius - 1f, y + radius - 1f); mTrackPath.arcTo(mTempRect, 180 + angle, -angle * 2); mTrackPath.close(); } if(x + radius < mDrawRect.right){ float angle2 = (float)Math.acos(Math.max(0f, (x + radius - mDrawRect.right + halfStroke) / halfStroke)); mTrackPath.moveTo((float) (mDrawRect.right - halfStroke + Math.cos(angle2) * halfStroke), (float) (y + Math.sin(angle2) * halfStroke)); angle2 = (float)(angle2 / Math.PI * 180); mTempRect.set(mDrawRect.right - mTrackSize, y - halfStroke, mDrawRect.right, y + halfStroke); mTrackPath.arcTo(mTempRect, angle2, -angle2 * 2); mTempRect.set(x - radius + 1f, y - radius + 1f, x + radius - 1f, y + radius - 1f); mTrackPath.arcTo(mTempRect, -angle, angle * 2); mTrackPath.close(); } } } @Override public void draw(@NonNull Canvas canvas) { super.draw(canvas); float x = (mDrawRect.width() - mThumbRadius * 2) * mThumbPosition + mDrawRect.left + mThumbRadius; float y = mDrawRect.centerY(); getTrackPath(x, y, mThumbRadius); mPaint.setColor(ColorUtil.getMiddleColor(getTrackColor(false), getTrackColor(true), mThumbPosition)); mPaint.setStyle(Paint.Style.FILL); canvas.drawPath(mTrackPath, mPaint); if(mShadowSize > 0){ int saveCount = canvas.save(); canvas.translate(x, y + mShadowOffset); canvas.drawPath(mShadowPath, mShadowPaint); canvas.restoreToCount(saveCount); } mPaint.setColor(ColorUtil.getMiddleColor(getThumbColor(false), getThumbColor(true), mThumbPosition)); mPaint.setStyle(Paint.Style.FILL); canvas.drawCircle(x, y, mThumbRadius, mPaint); } private void resetAnimation(){ mStartTime = SystemClock.uptimeMillis(); mStartPosition = mThumbPosition; mAnimDuration = (int)(mMaxAnimDuration * (mChecked ? (1f - mStartPosition) : mStartPosition)); } private void startAnimation() { if(getHandler() != null){ resetAnimation(); mRunning = true; getHandler().postAtTime(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); } else mThumbPosition = mChecked ? 1f : 0f; invalidate(); } private void stopAnimation() { mRunning = false; mThumbPosition = mChecked ? 1f : 0f; if(getHandler() != null) getHandler().removeCallbacks(mUpdater); invalidate(); } private final Runnable mUpdater = new Runnable() { @Override public void run() { update(); } }; private void update(){ long curTime = SystemClock.uptimeMillis(); float progress = Math.min(1f, (float)(curTime - mStartTime) / mAnimDuration); float value = mInterpolator.getInterpolation(progress); mThumbPosition = mChecked ? (mStartPosition * (1 - value) + value) : (mStartPosition * (1 - value)); if(progress == 1f) stopAnimation(); if(mRunning) { if(getHandler() != null) getHandler().postAtTime(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION); else stopAnimation(); } invalidate(); } @Override protected Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); ss.checked = isChecked(); return ss; } @Override protected void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); setChecked(ss.checked); requestLayout(); } static class SavedState extends BaseSavedState { boolean checked; /** * Constructor called from {@link Switch#onSaveInstanceState()} */ SavedState(Parcelable superState) { super(superState); } /** * Constructor called from {@link #CREATOR} */ private SavedState(Parcel in) { super(in); checked = (Boolean)in.readValue(null); } @Override public void writeToParcel(@NonNull Parcel out, int flags) { super.writeToParcel(out, flags); out.writeValue(checked); } @Override public String toString() { return "Switch.SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " checked=" + checked + "}"; } 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]; } }; } }
—> Run Your Code.
Leave a Reply