diff --git a/.idea/dictionaries/Administrator.xml b/.idea/dictionaries/Administrator.xml
new file mode 100644
index 00000000..d87a1619
--- /dev/null
+++ b/.idea/dictionaries/Administrator.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 00000000..d64dffbf
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 00000000..3b312839
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Material.iml b/Material.iml
index 0bb6048a..5da3c893 100644
--- a/Material.iml
+++ b/Material.iml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/app.iml b/app/app.iml
index 3d634f67..4d8d3d12 100644
--- a/app/app.iml
+++ b/app/app.iml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/java/com/rey/material/demo/ButtonFragment.java b/app/src/main/java/com/rey/material/demo/ButtonFragment.java
index 472ea988..6de9f773 100644
--- a/app/src/main/java/com/rey/material/demo/ButtonFragment.java
+++ b/app/src/main/java/com/rey/material/demo/ButtonFragment.java
@@ -47,7 +47,11 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa
@Override
public void onClick(View v) {
- Toast.makeText(getActivity(), "Button Clicked!\nEvent's fired when in anim end.", Toast.LENGTH_SHORT).show();
+ Toast.makeText(getActivity(), "Button Clicked!\nEvent's fired when in anim end.", Toast.LENGTH_SHORT).show();
+ if(v instanceof FloatingActionButton){
+ FloatingActionButton bt = (FloatingActionButton)v;
+ bt.setLineMorphingState((bt.getLineMorphingState() + 1) % 2, true);
+ }
}
};
@@ -55,7 +59,11 @@ public void onClick(View v) {
@Override
public void onClick(View v) {
- Toast.makeText(getActivity(), "Button Clicked!\nEvent's fired when out anim end.", Toast.LENGTH_SHORT).show();
+ Toast.makeText(getActivity(), "Button Clicked!\nEvent's fired when out anim end.", Toast.LENGTH_SHORT).show();
+ if(v instanceof FloatingActionButton){
+ FloatingActionButton bt = (FloatingActionButton)v;
+ bt.setLineMorphingState((bt.getLineMorphingState() + 1) % 2, true);
+ }
}
};
diff --git a/app/src/main/java/com/rey/material/demo/CustomViewPager.java b/app/src/main/java/com/rey/material/demo/CustomViewPager.java
new file mode 100644
index 00000000..4d16a4c7
--- /dev/null
+++ b/app/src/main/java/com/rey/material/demo/CustomViewPager.java
@@ -0,0 +1,33 @@
+package com.rey.material.demo;
+
+import android.content.Context;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.rey.material.widget.Slider;
+import com.rey.material.widget.Switch;
+
+/**
+ * Created by Rey on 3/18/2015.
+ */
+public class CustomViewPager extends ViewPager{
+
+ public CustomViewPager(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CustomViewPager(Context context) {
+ super(context);
+ }
+
+ protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
+ return super.canScroll(v, checkV, dx, x, y) || (checkV && customCanScroll(v));
+ }
+
+ protected boolean customCanScroll(View v) {
+ if (v instanceof Slider || v instanceof Switch)
+ return true;
+ return false;
+ }
+}
diff --git a/app/src/main/java/com/rey/material/demo/FabFragment.java b/app/src/main/java/com/rey/material/demo/FabFragment.java
new file mode 100644
index 00000000..c57d891c
--- /dev/null
+++ b/app/src/main/java/com/rey/material/demo/FabFragment.java
@@ -0,0 +1,64 @@
+package com.rey.material.demo;
+
+import android.annotation.TargetApi;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.rey.material.widget.FloatingActionButton;
+
+public class FabFragment extends Fragment{
+
+
+ public static FabFragment newInstance(){
+ FabFragment fragment = new FabFragment();
+
+ return fragment;
+ }
+
+ private Drawable[] mDrawables = new Drawable[2];
+ private int index = 0;
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.fragment_fab, container, false);
+
+ final FloatingActionButton fab_line = (FloatingActionButton)v.findViewById(R.id.fab_line);
+ fab_line.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ fab_line.setLineMorphingState((fab_line.getLineMorphingState() + 1) % 2, true);
+ }
+ });
+
+ final FloatingActionButton fab_image = (FloatingActionButton)v.findViewById(R.id.fab_image);
+ mDrawables[0] = v.getResources().getDrawable(R.drawable.ic_autorenew_white_24dp);
+ mDrawables[1] = v.getResources().getDrawable(R.drawable.ic_done_white_24dp);
+ fab_image.setIcon(mDrawables[index], false);
+ fab_image.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ index = (index + 1) % 2;
+ fab_image.setIcon(mDrawables[index], true);
+ }
+ });
+
+ return v;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+}
diff --git a/app/src/main/java/com/rey/material/demo/MainActivity.java b/app/src/main/java/com/rey/material/demo/MainActivity.java
index 3178d7ff..70607e17 100644
--- a/app/src/main/java/com/rey/material/demo/MainActivity.java
+++ b/app/src/main/java/com/rey/material/demo/MainActivity.java
@@ -37,7 +37,7 @@ public class MainActivity extends ActionBarActivity implements AdapterView.OnIte
private DrawerLayout dl_navigator;
private FrameLayout fl_drawer;
private ListView lv_drawer;
- private ViewPager vp;
+ private CustomViewPager vp;
private TabPageIndicator tpi;
private DrawerAdapter mDrawerAdapter;
@@ -47,7 +47,7 @@ public class MainActivity extends ActionBarActivity implements AdapterView.OnIte
private ToolbarManager mToolbarManager;
private SnackBar mSnackBar;
- private Tab[] mItems = new Tab[]{Tab.PROGRESS, Tab.BUTTONS, Tab.SWITCHES, Tab.TEXTFIELDS, Tab.SNACKBARS, Tab.DIALOGS};
+ private Tab[] mItems = new Tab[]{Tab.PROGRESS, Tab.BUTTONS, Tab.FAB, Tab.SWITCHES, Tab.SLIDERS, Tab.TEXTFIELDS, Tab.SNACKBARS, Tab.DIALOGS};
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -59,7 +59,7 @@ protected void onCreate(Bundle savedInstanceState) {
fl_drawer = (FrameLayout)findViewById(R.id.main_fl_drawer);
lv_drawer = (ListView)findViewById(R.id.main_lv_drawer);
mToolbar = (Toolbar)findViewById(R.id.main_toolbar);
- vp = (ViewPager)findViewById(R.id.main_vp);
+ vp = (CustomViewPager)findViewById(R.id.main_vp);
tpi = (TabPageIndicator)findViewById(R.id.main_tpi);
mSnackBar = (SnackBar)findViewById(R.id.main_sn);
@@ -109,7 +109,7 @@ public void onPageScrollStateChanged(int state) {}
});
- vp.setCurrentItem(0);
+ vp.setCurrentItem(2);
}
@Override
@@ -153,14 +153,14 @@ public SnackBar getSnackBar(){
return mSnackBar;
}
-
-
public enum Tab {
PROGRESS ("Progresses"),
BUTTONS ("Buttons"),
+ FAB ("FABs"),
SWITCHES ("Switches"),
- TEXTFIELDS ("Textfields"),
- SNACKBARS ("Snackbars"),
+ SLIDERS ("Sliders"),
+ TEXTFIELDS ("TextFields"),
+ SNACKBARS ("SnackBars"),
DIALOGS ("Dialogs");
private final String name;
@@ -260,8 +260,12 @@ public PagerAdapter(FragmentManager fm, Tab[] tabs) {
setFragment(Tab.PROGRESS, fragment);
else if(fragment instanceof ButtonFragment)
setFragment(Tab.BUTTONS, fragment);
+ else if(fragment instanceof FabFragment)
+ setFragment(Tab.FAB, fragment);
else if(fragment instanceof SwitchesFragment)
setFragment(Tab.SWITCHES, fragment);
+ else if(fragment instanceof SliderFragment)
+ setFragment(Tab.SLIDERS, fragment);
else if(fragment instanceof TextfieldFragment)
setFragment(Tab.TEXTFIELDS, fragment);
else if(fragment instanceof SnackbarFragment)
@@ -292,9 +296,15 @@ public Fragment getItem(int position) {
case BUTTONS:
mFragments[position] = ButtonFragment.newInstance();
break;
+ case FAB:
+ mFragments[position] = FabFragment.newInstance();
+ break;
case SWITCHES:
mFragments[position] = SwitchesFragment.newInstance();
break;
+ case SLIDERS:
+ mFragments[position] = SliderFragment.newInstance();
+ break;
case TEXTFIELDS:
mFragments[position] = TextfieldFragment.newInstance();
break;
diff --git a/app/src/main/java/com/rey/material/demo/SliderFragment.java b/app/src/main/java/com/rey/material/demo/SliderFragment.java
new file mode 100644
index 00000000..93a0256f
--- /dev/null
+++ b/app/src/main/java/com/rey/material/demo/SliderFragment.java
@@ -0,0 +1,41 @@
+package com.rey.material.demo;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CompoundButton;
+
+import com.rey.material.widget.RadioButton;
+
+public class SliderFragment extends Fragment{
+
+
+ public static SliderFragment newInstance(){
+ SliderFragment fragment = new SliderFragment();
+
+ return fragment;
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.fragment_slider, container, false);
+
+ return v;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index a49e9c70..80e44c19 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -32,7 +32,7 @@
android:clipToPadding="false"
android:background="?attr/colorPrimary"/>
-
diff --git a/app/src/main/res/layout/fragment_button.xml b/app/src/main/res/layout/fragment_button.xml
index c8affec3..a00c17de 100644
--- a/app/src/main/res/layout/fragment_button.xml
+++ b/app/src/main/res/layout/fragment_button.xml
@@ -10,7 +10,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
- android:gravity="center">
+ android:gravity="center"
+ android:padding="8dp">
+ android:gravity="center"
+ android:padding="8dp">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_progress.xml b/app/src/main/res/layout/fragment_progress.xml
index eb474e85..0e99dd7e 100644
--- a/app/src/main/res/layout/fragment_progress.xml
+++ b/app/src/main/res/layout/fragment_progress.xml
@@ -9,7 +9,8 @@
+ android:orientation="vertical"
+ android:padding="8dp">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_snackbar.xml b/app/src/main/res/layout/fragment_snackbar.xml
index 23bb0261..e857cefc 100644
--- a/app/src/main/res/layout/fragment_snackbar.xml
+++ b/app/src/main/res/layout/fragment_snackbar.xml
@@ -10,7 +10,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
- android:gravity="center">
+ android:gravity="center"
+ android:padding="8dp">
+ android:gravity="center"
+ android:padding="8dp">
+ android:checked="false"
+ android:padding="16dp"/>
- #FFDFDFDF
- 4dp
- - @style/FloatingActionButtonIcon.Light
- - true
+ - @style/FloatingActionButtonIcon.Light
- @style/Material.Drawable.Ripple.Touch.Light
- false
@@ -193,8 +192,7 @@
@@ -202,8 +200,7 @@
@@ -211,8 +208,7 @@
diff --git a/build.gradle b/build.gradle
index 6356aabd..4722e373 100644
--- a/build.gradle
+++ b/build.gradle
@@ -12,7 +12,14 @@ buildscript {
}
}
+def isReleaseBuild() {
+ return version.contains("SNAPSHOT") == false
+}
+
allprojects {
+ version = VERSION_NAME
+ group = GROUP
+
repositories {
jcenter()
}
diff --git a/gradle.properties b/gradle.properties
index 1d3591c8..6dfcee51 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -15,4 +15,19 @@
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
\ No newline at end of file
+# org.gradle.parallel=true
+
+VERSION_NAME=1.0.0
+VERSION_CODE=2
+GROUP=com.github.rey5137
+
+POM_DESCRIPTION= An Android library to bring Material Design UI to pre-Lolipop Android.
+POM_URL=https://github.com/rey5137/Material
+POM_SCM_URL=https://github.com/rey5137/Material
+POM_SCM_CONNECTION=scm:git@github.com:rey5137/Material.git
+POM_SCM_DEV_CONNECTION=scm:git@github.com:rey5137/Material.git
+POM_LICENCE_NAME=The Apache Software License, Version 2.0
+POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
+POM_LICENCE_DIST=repo
+POM_DEVELOPER_ID=rey5137
+POM_DEVELOPER_NAME=Rey Pham
\ No newline at end of file
diff --git a/image/button_flat_touch.gif b/image/button_flat_touch.gif
new file mode 100644
index 00000000..403cc3bf
Binary files /dev/null and b/image/button_flat_touch.gif differ
diff --git a/image/button_flat_wave.gif b/image/button_flat_wave.gif
new file mode 100644
index 00000000..73c78dc5
Binary files /dev/null and b/image/button_flat_wave.gif differ
diff --git a/image/button_raise_touch.gif b/image/button_raise_touch.gif
new file mode 100644
index 00000000..58b74b8c
Binary files /dev/null and b/image/button_raise_touch.gif differ
diff --git a/image/button_raise_wave.gif b/image/button_raise_wave.gif
new file mode 100644
index 00000000..50496d57
Binary files /dev/null and b/image/button_raise_wave.gif differ
diff --git a/image/cb.gif b/image/cb.gif
new file mode 100644
index 00000000..203cd484
Binary files /dev/null and b/image/cb.gif differ
diff --git a/image/fab_image.gif b/image/fab_image.gif
new file mode 100644
index 00000000..921cface
Binary files /dev/null and b/image/fab_image.gif differ
diff --git a/image/fab_line.gif b/image/fab_line.gif
new file mode 100644
index 00000000..f68d52e7
Binary files /dev/null and b/image/fab_line.gif differ
diff --git a/image/progress_circular_determinate.gif b/image/progress_circular_determinate.gif
new file mode 100644
index 00000000..5e271f77
Binary files /dev/null and b/image/progress_circular_determinate.gif differ
diff --git a/image/progress_circular_indeterminate.gif b/image/progress_circular_indeterminate.gif
new file mode 100644
index 00000000..97159abb
Binary files /dev/null and b/image/progress_circular_indeterminate.gif differ
diff --git a/image/progress_linear_buffer.gif b/image/progress_linear_buffer.gif
new file mode 100644
index 00000000..b78fa547
Binary files /dev/null and b/image/progress_linear_buffer.gif differ
diff --git a/image/progress_linear_determinate.gif b/image/progress_linear_determinate.gif
new file mode 100644
index 00000000..1614cfb1
Binary files /dev/null and b/image/progress_linear_determinate.gif differ
diff --git a/image/progress_linear_indeterminate.gif b/image/progress_linear_indeterminate.gif
new file mode 100644
index 00000000..6c203395
Binary files /dev/null and b/image/progress_linear_indeterminate.gif differ
diff --git a/image/progress_linear_query.gif b/image/progress_linear_query.gif
new file mode 100644
index 00000000..9bbe4740
Binary files /dev/null and b/image/progress_linear_query.gif differ
diff --git a/image/rb.gif b/image/rb.gif
new file mode 100644
index 00000000..ae77704d
Binary files /dev/null and b/image/rb.gif differ
diff --git a/image/slider_continuous.gif b/image/slider_continuous.gif
new file mode 100644
index 00000000..f01b4e8c
Binary files /dev/null and b/image/slider_continuous.gif differ
diff --git a/image/slider_discrete.gif b/image/slider_discrete.gif
new file mode 100644
index 00000000..d1ef3b98
Binary files /dev/null and b/image/slider_discrete.gif differ
diff --git a/image/switch.gif b/image/switch.gif
new file mode 100644
index 00000000..cfc8e034
Binary files /dev/null and b/image/switch.gif differ
diff --git a/image/textfield.gif b/image/textfield.gif
new file mode 100644
index 00000000..11b5ec95
Binary files /dev/null and b/image/textfield.gif differ
diff --git a/lib/build.gradle b/lib/build.gradle
index 10422b28..e706137e 100644
--- a/lib/build.gradle
+++ b/lib/build.gradle
@@ -23,3 +23,5 @@ dependencies {
compile 'com.android.support:appcompat-v7:21.0.2'
compile 'com.android.support:cardview-v7:21.0.2'
}
+
+apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/master/gradle-mvn-push.gradle'
\ No newline at end of file
diff --git a/lib/gradle.properties b/lib/gradle.properties
new file mode 100644
index 00000000..b06c0557
--- /dev/null
+++ b/lib/gradle.properties
@@ -0,0 +1,3 @@
+POM_NAME=Material Library
+POM_ARTIFACT_ID=material
+POM_PACKAGING=aar
\ No newline at end of file
diff --git a/lib/lib.iml b/lib/lib.iml
index db72899b..461e9f32 100644
--- a/lib/lib.iml
+++ b/lib/lib.iml
@@ -1,5 +1,5 @@
-
+
@@ -62,6 +62,7 @@
+
@@ -81,7 +82,9 @@
+
+
diff --git a/lib/src/main/AndroidManifest.xml b/lib/src/main/AndroidManifest.xml
index 631bc861..536642f2 100644
--- a/lib/src/main/AndroidManifest.xml
+++ b/lib/src/main/AndroidManifest.xml
@@ -1,7 +1,7 @@
+ android:versionCode="2"
+ android:versionName="1.0.0">
diff --git a/lib/src/main/java/com/rey/material/widget/EditText.java b/lib/src/main/java/com/rey/material/widget/EditText.java
index 9e1e6953..05685aea 100644
--- a/lib/src/main/java/com/rey/material/widget/EditText.java
+++ b/lib/src/main/java/com/rey/material/widget/EditText.java
@@ -160,8 +160,6 @@ private void init(Context context, AttributeSet attrs, int defStyleAttr, int def
mDividerCompoundPadding = a.getBoolean(R.styleable.EditText_et_dividerCompoundPadding, true);
mInputView.setPadding(0, 0, 0, dividerPadding + dividerHeight);
- System.out.println("a " + mInputView.getTotalPaddingLeft() + " " + mInputView.getTotalPaddingRight());
-
mDivider = new DividerDrawable(dividerHeight, mDividerCompoundPadding ? mInputView.getTotalPaddingLeft() : 0, mDividerCompoundPadding ? mInputView.getTotalPaddingRight() : 0, mDividerColors, dividerAnimDuration);
mDivider.setInEditMode(isInEditMode());
mDivider.setAnimEnable(false);
diff --git a/lib/src/main/java/com/rey/material/widget/FloatingActionButton.java b/lib/src/main/java/com/rey/material/widget/FloatingActionButton.java
index 8e811770..b5199739 100644
--- a/lib/src/main/java/com/rey/material/widget/FloatingActionButton.java
+++ b/lib/src/main/java/com/rey/material/widget/FloatingActionButton.java
@@ -14,13 +14,19 @@
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
+import android.view.View;
import android.view.ViewGroup;
-import android.widget.Button;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
@@ -28,13 +34,17 @@
import com.rey.material.drawable.LineMorphingDrawable;
import com.rey.material.drawable.RippleDrawable;
import com.rey.material.util.ThemeUtil;
+import com.rey.material.util.ViewUtil;
-public class FloatingActionButton extends Button {
+public class FloatingActionButton extends View {
private OvalShadowDrawable mBackground;
- private LineMorphingDrawable mIcon;
+ private Drawable mIcon;
+ private Drawable mPrevIcon;
+ private int mAnimDuration;
+ private Interpolator mInterpolator;
+ private SwitchIconAnimator mSwitchIconAnimator;
private int mIconSize;
- private boolean mAutoSwitch;
private RippleManager mRippleManager = new RippleManager();
private RippleDrawable mRipple;
@@ -66,51 +76,68 @@ public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAtt
init(context, attrs, defStyleAttr, defStyleRes);
}
-
- @SuppressWarnings("deprecation")
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
- public void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FloatingActionButton, defStyleAttr, defStyleRes);
-
- int radius = a.getDimensionPixelSize(R.styleable.FloatingActionButton_fab_radius, ThemeUtil.dpToPx(context, 28));
- int elevation = a.getDimensionPixelSize(R.styleable.FloatingActionButton_fab_elevation, ThemeUtil.dpToPx(context, 4));
- int bgColor = a.getColor(R.styleable.FloatingActionButton_fab_backgroundColor, ThemeUtil.colorAccent(context, 0xFFFAFAFA));
- int iconId = a.getResourceId(R.styleable.FloatingActionButton_fab_icon, 0);
- mIconSize = a.getDimensionPixelSize(R.styleable.FloatingActionButton_fab_iconSize, ThemeUtil.dpToPx(context, 24));
- mAutoSwitch = a.getBoolean(R.styleable.FloatingActionButton_fab_autoSwitch, true);
- int rippleId = a.getResourceId(R.styleable.FloatingActionButton_ripple, 0);
- boolean delayClick = a.getBoolean(R.styleable.FloatingActionButton_delayClick, false);
-
- a.recycle();
-
- mBackground = new OvalShadowDrawable(radius, bgColor, elevation, elevation);
-
- if(iconId != 0)
- setIcon(new LineMorphingDrawable.Builder(context, iconId).build());
-
- mRippleManager.onCreate(this, context, null, 0, 0);
- mRippleManager.setDelayClick(delayClick);
-
- if(rippleId != 0){
- RippleDrawable.Builder buidler = new RippleDrawable.Builder(context, rippleId);
-
- buidler.maskType(RippleDrawable.Mask.TYPE_OVAL)
- .backgroundDrawable(null)
- .left((int)mBackground.getPaddingLeft())
- .top((int)mBackground.getPaddingTop())
- .right((int)mBackground.getPaddingRight())
- .bottom((int)mBackground.getPaddingBottom());
-
- mRipple = buidler.build();
-
- if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
- setBackground(mRipple);
- else
- setBackgroundDrawable(mRipple);
- }
-
+
+ private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
setClickable(true);
+ mSwitchIconAnimator = new SwitchIconAnimator();
+ applyStyle(context, attrs, defStyleAttr, defStyleRes);
}
+
+ public void applyStyle(int resId){
+ applyStyle(getContext(), null, 0, resId);
+ }
+
+ private void applyStyle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FloatingActionButton, defStyleAttr, defStyleRes);
+
+ int radius = a.getDimensionPixelSize(R.styleable.FloatingActionButton_fab_radius, ThemeUtil.dpToPx(context, 28));
+ int elevation = a.getDimensionPixelSize(R.styleable.FloatingActionButton_fab_elevation, ThemeUtil.dpToPx(context, 4));
+ int bgColor = a.getColor(R.styleable.FloatingActionButton_fab_backgroundColor, ThemeUtil.colorAccent(context, 0xFFFAFAFA));
+ int iconSrc = a.getResourceId(R.styleable.FloatingActionButton_fab_iconSrc, 0);
+ int iconLineMorphing = a.getResourceId(R.styleable.FloatingActionButton_fab_iconLineMorphing, 0);
+ mIconSize = a.getDimensionPixelSize(R.styleable.FloatingActionButton_fab_iconSize, ThemeUtil.dpToPx(context, 24));
+ int rippleId = a.getResourceId(R.styleable.FloatingActionButton_ripple, 0);
+ boolean delayClick = a.getBoolean(R.styleable.FloatingActionButton_delayClick, false);
+ mAnimDuration = a.getInteger(R.styleable.FloatingActionButton_fab_animDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime));
+ int resId = a.getResourceId(R.styleable.FloatingActionButton_fab_interpolator, 0);
+ if(resId != 0)
+ mInterpolator = AnimationUtils.loadInterpolator(context, resId);
+ else if(mInterpolator == null)
+ mInterpolator = new DecelerateInterpolator();
+
+ a.recycle();
+
+ mBackground = new OvalShadowDrawable(radius, bgColor, elevation, elevation);
+ mBackground.setBounds(0, 0, getWidth(), getHeight());
+
+ if(iconLineMorphing != 0)
+ setIcon(new LineMorphingDrawable.Builder(context, iconLineMorphing).build(), false);
+ else if(iconSrc != 0)
+ setIcon(context.getResources().getDrawable(iconSrc), false);
+
+ mRippleManager.onCreate(this, context, null, 0, 0);
+ mRippleManager.setDelayClick(delayClick);
+
+ if(rippleId != 0){
+ RippleDrawable.Builder builder = new RippleDrawable.Builder(context, rippleId);
+
+ builder.maskType(RippleDrawable.Mask.TYPE_OVAL)
+ .backgroundDrawable(null)
+ .left((int)mBackground.getPaddingLeft())
+ .top((int)mBackground.getPaddingTop())
+ .right((int)mBackground.getPaddingRight())
+ .bottom((int)mBackground.getPaddingBottom());
+
+ mRipple = builder.build();
+
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
+ setBackground(mRipple);
+ else
+ setBackgroundDrawable(mRipple);
+ }
+
+ setClickable(true);
+ }
public int getRadius(){
return mBackground.getRadius();
@@ -139,35 +166,46 @@ else if(mBackground.setShadow(elevation, elevation))
requestLayout();
}
- public int getIconState(){
- if(mIcon != null)
- return mIcon.getLineState();
+ public int getLineMorphingState(){
+ if(mIcon != null && mIcon instanceof LineMorphingDrawable)
+ return ((LineMorphingDrawable)mIcon).getLineState();
return -1;
}
- public void setIconState(int state, boolean animation){
- if(mIcon != null)
- mIcon.switchLineState(state, animation);
+ public void setLineMorphingState(int state, boolean animation){
+ if(mIcon != null && mIcon instanceof LineMorphingDrawable)
+ ((LineMorphingDrawable)mIcon).switchLineState(state, animation);
}
public int getBackgroundColor(){
return mBackground.getColor();
}
- public LineMorphingDrawable getIcon(){
+ public Drawable getIcon(){
return mIcon;
}
- public void setIcon(LineMorphingDrawable icon){
- if(mIcon != null){
- mIcon.setCallback(null);
- unscheduleDrawable(mIcon);
- }
-
- mIcon = icon;
- mIcon.setCallback(this);
- invalidate();
+ public void setIcon(Drawable icon, boolean animation){
+ if(icon == null)
+ return;
+
+ if(animation) {
+ mSwitchIconAnimator.startAnimation(icon);
+ invalidate();
+ }
+ else{
+ if(mIcon != null){
+ mIcon.setCallback(null);
+ unscheduleDrawable(mIcon);
+ }
+
+ mIcon = icon;
+ float half = mIconSize / 2f;
+ mIcon.setBounds((int)(mBackground.getCenterX() - half), (int)(mBackground.getCenterY() - half), (int)(mBackground.getCenterX() + half), (int)(mBackground.getCenterY() + half));
+ mIcon.setCallback(this);
+ invalidate();
+ }
}
@Override
@@ -267,18 +305,10 @@ public void dismiss(){
if(getParent() != null)
((ViewGroup)getParent()).removeView(this);
}
-
- @Override
- public boolean performClick() {
- if(mIcon != null && mAutoSwitch)
- mIcon.switchLineState((mIcon.getLineState() + 1) % mIcon.getLineStateCount(), true);
-
- return super.performClick();
- }
-
+
@Override
protected boolean verifyDrawable(Drawable who) {
- return super.verifyDrawable(who) || mBackground == who || mIcon == who;
+ return super.verifyDrawable(who) || mBackground == who || mIcon == who || mPrevIcon == who;
}
@Override
@@ -294,12 +324,19 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
float half = mIconSize / 2f;
mIcon.setBounds((int)(mBackground.getCenterX() - half), (int)(mBackground.getCenterY() - half), (int)(mBackground.getCenterX() + half), (int)(mBackground.getCenterY() + half));
}
+
+ if(mPrevIcon != null){
+ float half = mIconSize / 2f;
+ mPrevIcon.setBounds((int)(mBackground.getCenterX() - half), (int)(mBackground.getCenterY() - half), (int)(mBackground.getCenterX() + half), (int)(mBackground.getCenterY() + half));
+ }
}
@Override
public void draw(@NonNull Canvas canvas) {
mBackground.draw(canvas);
super.draw(canvas);
+ if(mPrevIcon != null)
+ mPrevIcon.draw(canvas);
if(mIcon != null)
mIcon.draw(canvas);
}
@@ -541,5 +578,133 @@ public int getOpacity() {
}
}
-
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+
+ SavedState ss = new SavedState(superState);
+
+ ss.state = getLineMorphingState();
+ return ss;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+
+ super.onRestoreInstanceState(ss.getSuperState());
+ if(ss.state >= 0)
+ setLineMorphingState(ss.state, false);
+ requestLayout();
+ }
+
+ static class SavedState extends BaseSavedState {
+ int state;
+
+ /**
+ * Constructor called from {@link Slider#onSaveInstanceState()}
+ */
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ /**
+ * Constructor called from {@link #CREATOR}
+ */
+ private SavedState(Parcel in) {
+ super(in);
+ state = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(state);
+ }
+
+ @Override
+ public String toString() {
+ return "FloatingActionButton.SavedState{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " state=" + state + "}";
+ }
+
+ public static final Creator CREATOR
+ = new Creator() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ class SwitchIconAnimator implements Runnable{
+
+ boolean mRunning = false;
+ long mStartTime;
+
+ public void resetAnimation(){
+ mStartTime = SystemClock.uptimeMillis();
+ mIcon.setAlpha(0);
+ mPrevIcon.setAlpha(255);
+ }
+
+ public boolean startAnimation(Drawable icon) {
+ if(mIcon == icon)
+ return false;
+
+ mPrevIcon = mIcon;
+ mIcon = icon;
+ float half = mIconSize / 2f;
+ mIcon.setBounds((int)(mBackground.getCenterX() - half), (int)(mBackground.getCenterY() - half), (int)(mBackground.getCenterX() + half), (int)(mBackground.getCenterY() + half));
+ mIcon.setCallback(FloatingActionButton.this);
+
+ if(getHandler() != null){
+ resetAnimation();
+ mRunning = true;
+ getHandler().postAtTime(this, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION);
+ }
+ else {
+ mPrevIcon.setCallback(null);
+ unscheduleDrawable(mPrevIcon);
+ mPrevIcon = null;
+ }
+
+ invalidate();
+ return true;
+ }
+
+ public void stopAnimation() {
+ mRunning = false;
+ mPrevIcon.setCallback(null);
+ unscheduleDrawable(mPrevIcon);
+ mPrevIcon = null;
+ mIcon.setAlpha(255);
+ getHandler().removeCallbacks(this);
+ invalidate();
+ }
+
+ @Override
+ public void run() {
+ long curTime = SystemClock.uptimeMillis();
+ float progress = Math.min(1f, (float)(curTime - mStartTime) / mAnimDuration);
+ float value = mInterpolator.getInterpolation(progress);
+
+ mIcon.setAlpha(Math.round(255 * value));
+ mPrevIcon.setAlpha(Math.round(255 * (1f - value)));
+
+ if(progress == 1f)
+ stopAnimation();
+
+ if(mRunning)
+ getHandler().postAtTime(this, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION);
+
+ invalidate();
+ }
+
+ }
}
diff --git a/lib/src/main/java/com/rey/material/widget/RippleManager.java b/lib/src/main/java/com/rey/material/widget/RippleManager.java
index 5b4b6def..284649a5 100644
--- a/lib/src/main/java/com/rey/material/widget/RippleManager.java
+++ b/lib/src/main/java/com/rey/material/widget/RippleManager.java
@@ -32,7 +32,7 @@ public void onCreate(View v, Context context, AttributeSet attrs, int defStyleAt
mDelayClick = a.getBoolean(R.styleable.RippleView_delayClick, mDelayClick);
a.recycle();
- if(resId != 0){
+ if(resId != 0 && !mView.isInEditMode()){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
mView.setBackground(new RippleDrawable.Builder(context, resId).build());
else
diff --git a/lib/src/main/java/com/rey/material/widget/Slider.java b/lib/src/main/java/com/rey/material/widget/Slider.java
new file mode 100644
index 00000000..43dd2a2e
--- /dev/null
+++ b/lib/src/main/java/com/rey/material/widget/Slider.java
@@ -0,0 +1,877 @@
+package com.rey.material.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+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 com.rey.material.R;
+import com.rey.material.util.ColorUtil;
+import com.rey.material.util.ThemeUtil;
+import com.rey.material.util.TypefaceUtil;
+import com.rey.material.util.ViewUtil;
+
+/**
+ * Created by Ret on 3/18/2015.
+ */
+public class Slider extends View{
+
+ private RippleManager mRippleManager = new RippleManager();
+
+ private Paint mPaint;
+ private RectF mDrawRect;
+ private RectF mTempRect;
+ private Path mLeftTrackPath;
+ private Path mRightTrackPath;
+ private Path mMarkPath;
+
+ private int mMinValue = 0;
+ private int mMaxValue = 100;
+ private int mStepValue = 1;
+
+ private boolean mDiscreteMode = false;
+
+ private int mPrimaryColor;
+ private int mSecondaryColor;
+ private int mTrackSize;
+ private Paint.Cap mTrackCap;
+ private int mThumbBorderSize;
+ private int mThumbRadius;
+ private int mThumbFocusRadius;
+ private float mThumbPosition;
+ private Typeface mTypeface;
+ private int mTextSize;
+ private int mTextColor;
+ private int mGravity = Gravity.CENTER;
+ private int mTravelAnimationDuration;
+ private int mTransformAnimationDuration;
+ private Interpolator mInterpolator;
+
+ private int mTouchSlop;
+ private PointF mMemoPoint;
+ private boolean mIsDragging;
+ private float mThumbCurrentRadius;
+ private float mThumbFillPercent;
+ private int mTextHeight;
+ private int mMemoValue;
+ private String mValueText;
+
+ private ThumbRadiusAnimator mThumbRadiusAnimator;
+ private ThumbStrokeAnimator mThumbStrokeAnimator;
+ private ThumbMoveAnimator mThumbMoveAnimator;
+
+ public Slider(Context context) {
+ super(context);
+
+ init(context, null, 0, 0);
+ }
+
+ public Slider(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ init(context, attrs, 0, 0);
+ }
+
+ public Slider(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ init(context, attrs, defStyleAttr, 0);
+ }
+
+ public Slider(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();
+ mLeftTrackPath = new Path();
+ mRightTrackPath = new Path();
+
+ mThumbRadiusAnimator = new ThumbRadiusAnimator();
+ mThumbStrokeAnimator = new ThumbStrokeAnimator();
+ mThumbMoveAnimator = new ThumbMoveAnimator();
+
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mMemoPoint = new PointF();
+
+ 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.Slider, defStyleAttr, defStyleRes);
+ mDiscreteMode = a.getBoolean(R.styleable.Slider_sl_discreteMode, mDiscreteMode);
+ mPrimaryColor = a.getColor(R.styleable.Slider_sl_primaryColor, ThemeUtil.colorControlActivated(context, 0xFF000000));
+ mSecondaryColor = a.getColor(R.styleable.Slider_sl_secondaryColor, ThemeUtil.colorControlNormal(context, 0xFF000000));
+ mTrackSize = a.getDimensionPixelSize(R.styleable.Slider_sl_trackSize, ThemeUtil.dpToPx(context, 2));
+ int cap = a.getInteger(R.styleable.Slider_sl_trackCap, 0);
+ if(cap == 0)
+ mTrackCap = Paint.Cap.BUTT;
+ else if(cap == 1)
+ mTrackCap = Paint.Cap.ROUND;
+ else
+ mTrackCap = Paint.Cap.SQUARE;
+ mThumbBorderSize = a.getDimensionPixelSize(R.styleable.Slider_sl_thumbBorderSize, ThemeUtil.dpToPx(context, 2));
+ mThumbRadius = a.getDimensionPixelSize(R.styleable.Slider_sl_thumbRadius, ThemeUtil.dpToPx(context, 10));
+ mThumbFocusRadius = a.getDimensionPixelSize(R.styleable.Slider_sl_thumbFocusRadius, ThemeUtil.dpToPx(context, 14));
+ mTravelAnimationDuration = a.getInteger(R.styleable.Slider_sl_travelAnimDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime));
+ mTransformAnimationDuration = a.getInteger(R.styleable.Slider_sl_travelAnimDuration, context.getResources().getInteger(android.R.integer.config_shortAnimTime));
+ int resId = a.getResourceId(R.styleable.Slider_sl_interpolator, 0);
+ mInterpolator = resId != 0 ? AnimationUtils.loadInterpolator(context, resId) : new DecelerateInterpolator();
+ mGravity = a.getInt(R.styleable.Slider_android_gravity, Gravity.CENTER_VERTICAL);
+ mMinValue = a.getInteger(R.styleable.Slider_sl_minValue, mMinValue);
+ mMaxValue = a.getInteger(R.styleable.Slider_sl_maxValue, mMaxValue);
+ mStepValue = a.getInteger(R.styleable.Slider_sl_stepValue, mStepValue);
+ setValue(a.getInteger(R.styleable.Slider_sl_value, getValue()), false);
+
+ String familyName = a.getString(R.styleable.Slider_sl_fontFamily);
+ int style = a.getInteger(R.styleable.Slider_sl_textStyle, Typeface.NORMAL);
+
+ mTypeface = TypefaceUtil.load(context, familyName, style);
+ mTextColor = a.getColor(R.styleable.Slider_sl_textColor, 0xFFFFFFFF);
+ mTextSize = a.getDimensionPixelSize(R.styleable.Slider_sl_textSize, context.getResources().getDimensionPixelOffset(R.dimen.abc_text_size_small_material));
+ setEnabled(a.getBoolean(R.styleable.Slider_android_enabled, true));
+
+ a.recycle();
+
+ mPaint.setTextSize(mTextSize);
+ mPaint.setTextAlign(Paint.Align.CENTER);
+ mPaint.setTypeface(mTypeface);
+
+ measureText();
+ }
+
+ private void measureText(){
+ Rect temp = new Rect();
+ String text = String.valueOf(mMaxValue);
+ mPaint.setTextSize(mTextSize);
+ float width = mPaint.measureText(text);
+ float maxWidth = (float)(mThumbRadius * Math.sqrt(2) * 2 - ThemeUtil.dpToPx(getContext(), 8));
+ if(width > maxWidth){
+ float textSize = mTextSize * maxWidth / width;
+ mPaint.setTextSize(textSize);
+ }
+
+ mPaint.getTextBounds(text, 0, text.length(), temp);
+ mTextHeight = temp.height();
+ }
+
+ private String getValueText(){
+ int value = getValue();
+ if(mValueText == null || mMemoValue != value){
+ mMemoValue = value;
+ mValueText = String.valueOf(mMemoValue);
+ }
+
+ return mValueText;
+ }
+
+ public int getValue(){
+ return Math.round(getExactValue());
+ }
+
+ public float getExactValue(){
+ return (mMaxValue - mMinValue) * getPosition() + mMinValue;
+ }
+
+ public float getPosition(){
+ return mThumbMoveAnimator.isRunning() ? mThumbMoveAnimator.getPosition() : mThumbPosition;
+ }
+
+ public void setPosition(float pos, boolean animation){
+ if(animation) {
+ if(!mThumbMoveAnimator.startAnimation(pos)){
+ if(!mIsDragging)
+ mThumbRadiusAnimator.startAnimation(mThumbRadius);
+ mThumbStrokeAnimator.startAnimation(pos == 0 ? 0 : 1);
+ }
+ }
+ else {
+ mThumbPosition = pos;
+ mThumbCurrentRadius = mThumbRadius;
+ mThumbFillPercent = mThumbPosition == 0 ? 0 : 1;
+ invalidate();
+ }
+ }
+
+ public void setValue(float value, boolean animation){
+ value = Math.min(mMaxValue, Math.max(value, mMinValue));
+ setPosition((value - mMinValue) / (mMaxValue - mMinValue), animation);
+ }
+
+ @Override
+ public void setOnClickListener(OnClickListener l) {
+ if(l == mRippleManager)
+ super.setOnClickListener(l);
+ else{
+ mRippleManager.setOnClickListener(l);
+ setOnClickListener(mRippleManager);
+ }
+ }
+
+ @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 (mDiscreteMode ? (int)(mThumbRadius * Math.sqrt(2)) : mThumbFocusRadius) * 4 + getPaddingLeft() + getPaddingRight();
+ }
+
+ @Override
+ public int getSuggestedMinimumHeight() {
+ return (mDiscreteMode ? (int)(mThumbRadius * (4 + Math.sqrt(2))) : mThumbFocusRadius * 2) + getPaddingTop() + getPaddingBottom();
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mDrawRect.left = getPaddingLeft() + mThumbRadius;
+ mDrawRect.right = w - getPaddingRight() - mThumbRadius;
+
+ int align = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+ if(mDiscreteMode){
+ int fullHeight = (int)(mThumbRadius * (4 + Math.sqrt(2)));
+ int height = mThumbRadius * 2;
+ switch (align) {
+ case Gravity.TOP:
+ mDrawRect.top = Math.max(getPaddingTop(), fullHeight - height);
+ mDrawRect.bottom = mDrawRect.top + height;
+ break;
+ case Gravity.BOTTOM:
+ mDrawRect.bottom = h - getPaddingBottom();
+ mDrawRect.top = mDrawRect.bottom - height;
+ break;
+ default:
+ mDrawRect.top = Math.max((h - height) / 2f, fullHeight - height);
+ mDrawRect.bottom = mDrawRect.top + height;
+ break;
+ }
+ }
+ else{
+ int height = mThumbFocusRadius * 2;
+ switch (align) {
+ case Gravity.TOP:
+ mDrawRect.top = getPaddingTop();
+ mDrawRect.bottom = mDrawRect.top + height;
+ break;
+ case Gravity.BOTTOM:
+ mDrawRect.bottom = h - getPaddingBottom();
+ mDrawRect.top = mDrawRect.bottom - height;
+ break;
+ default:
+ mDrawRect.top = (h - height) / 2f;
+ mDrawRect.bottom = mDrawRect.top + height;
+ break;
+ }
+ }
+ }
+
+ private boolean isThumbHit(float x, float y, float radius){
+ float cx = mDrawRect.width() * mThumbPosition + mDrawRect.left;
+ float cy = mDrawRect.centerY();
+
+ return x >= cx - radius && x <= cx + radius && y >= cy - radius && y < cy + radius;
+ }
+
+ private double distance(float x1, float y1, float x2, float y2){
+ return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
+ }
+
+ private float correctPosition(float position){
+ if(!mDiscreteMode)
+ return position;
+
+ int totalOffset = mMaxValue - mMinValue;
+ int valueOffset = Math.round(totalOffset * position);
+ int stepOffset = valueOffset / mStepValue;
+ int lowerValue = stepOffset * mStepValue;
+ int higherValue = Math.min(mMaxValue, (stepOffset + 1) * mStepValue);
+
+ if(valueOffset - lowerValue < higherValue - valueOffset)
+ position = lowerValue / (float)totalOffset;
+ else
+ position = higherValue / (float)totalOffset;
+
+ return position;
+ }
+
+ @Override
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ super.onTouchEvent(event);
+ mRippleManager.onTouchEvent(event);
+
+ if(!isEnabled())
+ return false;
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mIsDragging = isThumbHit(event.getX(), event.getY(), mThumbRadius) && !mThumbMoveAnimator.isRunning();
+ mMemoPoint.set(event.getX(), event.getY());
+ if(mIsDragging)
+ mThumbRadiusAnimator.startAnimation(mDiscreteMode ? 0 : mThumbFocusRadius);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if(mIsDragging) {
+ if(mDiscreteMode) {
+ float position = correctPosition(Math.min(1f, Math.max(0f, (event.getX() - mDrawRect.left) / mDrawRect.width())));
+ setPosition(position, true);
+ }
+ else{
+ float offset = (event.getX() - mMemoPoint.x) / mDrawRect.width();
+ mThumbPosition = Math.min(1f, Math.max(0f, mThumbPosition + offset));
+ mMemoPoint.x = event.getX();
+ mThumbStrokeAnimator.startAnimation(mThumbPosition == 0 ? 0 : 1);
+ invalidate();
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if(mIsDragging) {
+ mIsDragging = false;
+ setPosition(getPosition(), true);
+ }
+ else if(distance(mMemoPoint.x, mMemoPoint.y, event.getX(), event.getY()) <= mTouchSlop){
+ float position = correctPosition(Math.min(1f, Math.max(0f, (event.getX() - mDrawRect.left) / mDrawRect.width())));
+ setPosition(position, true);
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ if(mIsDragging) {
+ mIsDragging = false;
+ setPosition(getPosition(), true);
+ }
+ break;
+ }
+
+ return true;
+ }
+
+ private void getTrackPath(float x, float y, float radius){
+ float halfStroke = mTrackSize / 2f;
+
+ mLeftTrackPath.reset();
+ mRightTrackPath.reset();
+
+ if(radius - 1f < halfStroke){
+ if(mTrackCap != Paint.Cap.ROUND){
+ if(x > mDrawRect.left){
+ mLeftTrackPath.moveTo(mDrawRect.left, y - halfStroke);
+ mLeftTrackPath.lineTo(x, y - halfStroke);
+ mLeftTrackPath.lineTo(x, y + halfStroke);
+ mLeftTrackPath.lineTo(mDrawRect.left, y + halfStroke);
+ mLeftTrackPath.close();
+ }
+
+ if(x < mDrawRect.right){
+ mRightTrackPath.moveTo(mDrawRect.right, y + halfStroke);
+ mRightTrackPath.lineTo(x, y + halfStroke);
+ mRightTrackPath.lineTo(x, y - halfStroke);
+ mRightTrackPath.lineTo(mDrawRect.right, y - halfStroke);
+ mRightTrackPath.close();
+ }
+ }
+ else{
+ if(x > mDrawRect.left){
+ mTempRect.set(mDrawRect.left, y - halfStroke, mDrawRect.left + mTrackSize, y + halfStroke);
+ mLeftTrackPath.arcTo(mTempRect, 90, 180);
+ mLeftTrackPath.lineTo(x, y - halfStroke);
+ mLeftTrackPath.lineTo(x, y + halfStroke);
+ mLeftTrackPath.close();
+ }
+
+ if(x < mDrawRect.right){
+ mTempRect.set(mDrawRect.right - mTrackSize, y - halfStroke, mDrawRect.right, y + halfStroke);
+ mRightTrackPath.arcTo(mTempRect, 270, 180);
+ mRightTrackPath.lineTo(x, y + halfStroke);
+ mRightTrackPath.lineTo(x, y - halfStroke);
+ mRightTrackPath.close();
+ }
+ }
+ }
+ else{
+ 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){
+ mLeftTrackPath.moveTo(mDrawRect.left, y - halfStroke);
+ mLeftTrackPath.arcTo(mTempRect, 180 + angle, -angle * 2);
+ mLeftTrackPath.lineTo(mDrawRect.left, y + halfStroke);
+ mLeftTrackPath.close();
+ }
+
+ if(x + radius < mDrawRect.right){
+ mRightTrackPath.moveTo(mDrawRect.right, y - halfStroke);
+ mRightTrackPath.arcTo(mTempRect, -angle, angle * 2);
+ mRightTrackPath.lineTo(mDrawRect.right, y + halfStroke);
+ mRightTrackPath.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);
+ mLeftTrackPath.arcTo(mTempRect, 180 - angle2, angle2 * 2);
+
+ mTempRect.set(x - radius + 1f, y - radius + 1f, x + radius - 1f, y + radius - 1f);
+ mLeftTrackPath.arcTo(mTempRect, 180 + angle, -angle * 2);
+ mLeftTrackPath.close();
+ }
+
+ if(x + radius < mDrawRect.right){
+ float angle2 = (float)Math.acos(Math.max(0f, (x + radius - mDrawRect.right + halfStroke) / halfStroke));
+ mRightTrackPath.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);
+ mRightTrackPath.arcTo(mTempRect, angle2, -angle2 * 2);
+
+ mTempRect.set(x - radius + 1f, y - radius + 1f, x + radius - 1f, y + radius - 1f);
+ mRightTrackPath.arcTo(mTempRect, -angle, angle * 2);
+ mRightTrackPath.close();
+ }
+ }
+ }
+ }
+
+ private Path getMarkPath(Path path, float cx, float cy, float radius, float factor){
+ if(path == null)
+ path = new Path();
+ else
+ path.reset();
+
+ float x1 = cx - radius;
+ float y1 = cy;
+ float x2 = cx + radius;
+ float y2 = cy;
+ float x3 = cx;
+ float y3 = cy + radius;
+
+ float nCx = cx;
+ float nCy = cy - radius * factor;
+
+ // calculate first arc
+ float angle = (float)(Math.atan2(y2 - nCy, x2 - nCx) * 180 / Math.PI);
+ float nRadius = (float)distance(nCx, nCy, x1, y1);
+ mTempRect.set(nCx - nRadius, nCy - nRadius, nCx + nRadius, nCy + nRadius);
+ path.moveTo(x1, y1);
+ path.arcTo(mTempRect, 180 - angle, 180 + angle * 2);
+
+ if(factor > 0.9f)
+ path.lineTo(x3, y3);
+ else{
+ // find center point for second arc
+ float x4 = (x2 + x3) / 2;
+ float y4 = (y2 + y3) / 2;
+
+ double d1 = distance(x2, y2, x4, y4);
+ double d2 = d1 / Math.tan(Math.PI * (1f - factor) / 4);
+
+ nCx = (float)(x4 - Math.cos(Math.PI / 4) * d2);
+ nCy = (float)(y4 - Math.sin(Math.PI / 4) * d2);
+
+ // calculate second arc
+ angle = (float)(Math.atan2(y2 - nCy, x2 - nCx) * 180 / Math.PI);
+ float angle2 = (float)(Math.atan2(y3 - nCy, x3 - nCx) * 180 / Math.PI);
+ nRadius = (float)distance(nCx, nCy, x2, y2);
+ mTempRect.set(nCx - nRadius, nCy - nRadius, nCx + nRadius, nCy + nRadius);
+ path.arcTo(mTempRect, angle, angle2 - angle);
+
+ // calculate third arc
+ nCx = cx * 2 - nCx;
+ angle = (float)(Math.atan2(y3 - nCy, x3 - nCx) * 180 / Math.PI);
+ angle2 = (float)(Math.atan2(y1 - nCy, x1 - nCx) * 180 / Math.PI);
+ mTempRect.set(nCx - nRadius, nCy - nRadius, nCx + nRadius, nCy + nRadius);
+ path.arcTo(mTempRect, angle + (float)Math.PI / 4, angle2 - angle);
+ }
+
+ path.close();
+
+ return path;
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas) {
+ super.draw(canvas);
+
+ float x = mDrawRect.width() * mThumbPosition + mDrawRect.left;
+ float y = mDrawRect.centerY();
+ int filledPrimaryColor = ColorUtil.getMiddleColor(mSecondaryColor, isEnabled() ? mPrimaryColor : mSecondaryColor, mThumbFillPercent);
+
+ getTrackPath(x, y, mThumbCurrentRadius);
+ mPaint.setStyle(Paint.Style.FILL);
+ mPaint.setColor(mSecondaryColor);
+ canvas.drawPath(mRightTrackPath, mPaint);
+ mPaint.setColor(filledPrimaryColor);
+ canvas.drawPath(mLeftTrackPath, mPaint);
+
+ if(mDiscreteMode){
+ float factor = 1f - mThumbCurrentRadius / mThumbRadius;
+
+ if(factor > 0){
+ mMarkPath = getMarkPath(mMarkPath, x, y, mThumbRadius, factor);
+ mPaint.setStyle(Paint.Style.FILL);
+ int saveCount = canvas.save();
+ canvas.translate(0, -mThumbRadius * 2 * factor);
+ canvas.drawPath(mMarkPath, mPaint);
+ mPaint.setColor(ColorUtil.getColor(mTextColor, factor));
+ canvas.drawText(getValueText(), x, y + mTextHeight / 2f - mThumbRadius * factor, mPaint);
+ canvas.restoreToCount(saveCount);
+ }
+
+ float radius = isEnabled() ? mThumbCurrentRadius : mThumbCurrentRadius - mThumbBorderSize;
+ if(radius > 0) {
+ mPaint.setColor(filledPrimaryColor);
+ canvas.drawCircle(x, y, radius, mPaint);
+ }
+ }
+ else{
+ float radius = isEnabled() ? mThumbCurrentRadius : mThumbCurrentRadius - mThumbBorderSize;
+ if(mThumbFillPercent == 1)
+ mPaint.setStyle(Paint.Style.FILL);
+ else{
+ float strokeWidth = (radius - mThumbBorderSize) * mThumbFillPercent + mThumbBorderSize;
+ radius = radius - strokeWidth / 2f;
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(strokeWidth);
+ }
+ canvas.drawCircle(x, y, radius, mPaint);
+ }
+ }
+
+ class ThumbRadiusAnimator implements Runnable{
+
+ boolean mRunning = false;
+ long mStartTime;
+ float mStartRadius;
+ int mRadius;
+
+ public void resetAnimation(){
+ mStartTime = SystemClock.uptimeMillis();
+ mStartRadius = mThumbCurrentRadius;
+ }
+
+ public boolean startAnimation(int radius) {
+ if(mThumbCurrentRadius == radius)
+ return false;
+
+ mRadius = radius;
+
+ if(getHandler() != null){
+ resetAnimation();
+ mRunning = true;
+ getHandler().postAtTime(this, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION);
+ }
+ else
+ mThumbCurrentRadius = mRadius;
+
+ invalidate();
+ return true;
+ }
+
+ public void stopAnimation() {
+ mRunning = false;
+ mThumbCurrentRadius = mRadius;
+ getHandler().removeCallbacks(this);
+ invalidate();
+ }
+
+ @Override
+ public void run() {
+ long curTime = SystemClock.uptimeMillis();
+ float progress = Math.min(1f, (float)(curTime - mStartTime) / mTransformAnimationDuration);
+ float value = mInterpolator.getInterpolation(progress);
+
+ mThumbCurrentRadius = (mRadius - mStartRadius) * value + mStartRadius;
+
+ if(progress == 1f)
+ stopAnimation();
+
+ if(mRunning)
+ getHandler().postAtTime(this, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION);
+
+ invalidate();
+ }
+
+ }
+
+ class ThumbStrokeAnimator implements Runnable{
+
+ boolean mRunning = false;
+ long mStartTime;
+ float mStartFillPercent;
+ int mFillPercent;
+
+ public void resetAnimation(){
+ mStartTime = SystemClock.uptimeMillis();
+ mStartFillPercent = mThumbFillPercent;
+ }
+
+ public boolean startAnimation(int fillPercent) {
+ if(mThumbFillPercent == fillPercent)
+ return false;
+
+ mFillPercent = fillPercent;
+
+ if(getHandler() != null){
+ resetAnimation();
+ mRunning = true;
+ getHandler().postAtTime(this, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION);
+ }
+ else
+ mThumbFillPercent = mFillPercent;
+
+ invalidate();
+ return true;
+ }
+
+ public void stopAnimation() {
+ mRunning = false;
+ mThumbFillPercent = mFillPercent;
+ getHandler().removeCallbacks(this);
+ invalidate();
+ }
+
+ @Override
+ public void run() {
+ long curTime = SystemClock.uptimeMillis();
+ float progress = Math.min(1f, (float)(curTime - mStartTime) / mTransformAnimationDuration);
+ float value = mInterpolator.getInterpolation(progress);
+
+ mThumbFillPercent = (mFillPercent - mStartFillPercent) * value + mStartFillPercent;
+
+ if(progress == 1f)
+ stopAnimation();
+
+ if(mRunning)
+ getHandler().postAtTime(this, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION);
+
+ invalidate();
+ }
+
+ }
+
+ class ThumbMoveAnimator implements Runnable{
+
+ boolean mRunning = false;
+ long mStartTime;
+ float mStartFillPercent;
+ float mStartRadius;
+ float mStartPosition;
+ float mPosition;
+ float mFillPercent;
+ int mDuration;
+
+ public boolean isRunning(){
+ return mRunning;
+ }
+
+ public float getPosition(){
+ return mPosition;
+ }
+
+ public void resetAnimation(){
+ mStartTime = SystemClock.uptimeMillis();
+ mStartPosition = mThumbPosition;
+ mStartFillPercent = mThumbFillPercent;
+ mStartRadius = mThumbCurrentRadius;
+ mFillPercent = mPosition == 0 ? 0 : 1;
+ mDuration = mDiscreteMode && !mIsDragging ? mTransformAnimationDuration * 2 + mTravelAnimationDuration : mTravelAnimationDuration;
+ }
+
+ public boolean startAnimation(float position) {
+ if(mThumbPosition == position)
+ return false;
+
+ mPosition = position;
+
+ if(getHandler() != null){
+ resetAnimation();
+ mRunning = true;
+ getHandler().postAtTime(this, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION);
+ }
+ else {
+ mThumbPosition = position;
+ mThumbCurrentRadius = mThumbRadius;
+ mThumbFillPercent = mThumbPosition == 0 ? 0 : 1;
+ }
+
+ invalidate();
+
+ return true;
+ }
+
+ public void stopAnimation() {
+ mRunning = false;
+ mThumbCurrentRadius = mDiscreteMode && mIsDragging ? 0 : mThumbRadius;
+ mThumbFillPercent = mFillPercent;
+ mThumbPosition = mPosition;
+ getHandler().removeCallbacks(this);
+ invalidate();
+ }
+
+ @Override
+ public void run() {
+ long curTime = SystemClock.uptimeMillis();
+ float progress = Math.min(1f, (float)(curTime - mStartTime) / mDuration);
+ float value = mInterpolator.getInterpolation(progress);
+
+ if(mDiscreteMode){
+ if(mIsDragging) {
+ mThumbPosition = (mPosition - mStartPosition) * value + mStartPosition;
+ mThumbFillPercent = (mFillPercent - mStartFillPercent) * value + mStartFillPercent;
+ }
+ else{
+ float p1 = (float)mTravelAnimationDuration / mDuration;
+ float p2 = (float)(mTravelAnimationDuration + mTransformAnimationDuration)/ mDuration;
+ if(progress < p1) {
+ value = mInterpolator.getInterpolation(progress / p1);
+ mThumbCurrentRadius = mStartRadius * (1f - value);
+ mThumbPosition = (mPosition - mStartPosition) * value + mStartPosition;
+ mThumbFillPercent = (mFillPercent - mStartFillPercent) * value + mStartFillPercent;
+ }
+ else if(progress > p2){
+ mThumbCurrentRadius = mThumbRadius * (progress - p2) / (1 - p2);
+ }
+ }
+ }
+ else{
+ mThumbPosition = (mPosition - mStartPosition) * value + mStartPosition;
+ mThumbFillPercent = (mFillPercent - mStartFillPercent) * value + mStartFillPercent;
+
+ if(progress < 0.2)
+ mThumbCurrentRadius = Math.max(mThumbRadius + mThumbBorderSize * progress * 5, mThumbCurrentRadius);
+ else if(progress >= 0.8)
+ mThumbCurrentRadius = mThumbRadius + mThumbBorderSize * (5f - progress * 5);
+ }
+
+
+ if(progress == 1f)
+ stopAnimation();
+
+ if(mRunning)
+ getHandler().postAtTime(this, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION);
+
+ invalidate();
+ }
+
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+
+ SavedState ss = new SavedState(superState);
+
+ ss.position = getPosition();
+ return ss;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+
+ super.onRestoreInstanceState(ss.getSuperState());
+ setPosition(ss.position, false);
+ requestLayout();
+ }
+
+ static class SavedState extends BaseSavedState {
+ float position;
+
+ /**
+ * Constructor called from {@link Slider#onSaveInstanceState()}
+ */
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ /**
+ * Constructor called from {@link #CREATOR}
+ */
+ private SavedState(Parcel in) {
+ super(in);
+ position = in.readFloat();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeFloat(position);
+ }
+
+ @Override
+ public String toString() {
+ return "Slider.SavedState{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " pos=" + position + "}";
+ }
+
+ public static final Creator CREATOR
+ = new Creator() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+}
diff --git a/lib/src/main/java/com/rey/material/widget/Switch.java b/lib/src/main/java/com/rey/material/widget/Switch.java
index 929677b2..fa6317a3 100644
--- a/lib/src/main/java/com/rey/material/widget/Switch.java
+++ b/lib/src/main/java/com/rey/material/widget/Switch.java
@@ -6,7 +6,9 @@
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.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
@@ -35,12 +37,11 @@ public class Switch extends View implements Checkable {
private Paint mPaint;
private RectF mDrawRect;
private RectF mTempRect;
- private Path mPath;
+ private Path mTrackPath;
- private int mStrokeSize;
- private ColorStateList mStrokeColors;
- private Paint.Cap mStrokeCap;
- private int mThumbBorderSize;
+ private int mTrackSize;
+ private ColorStateList mTrackColors;
+ private Paint.Cap mTrackCap;
private int mThumbRadius;
private ColorStateList mThumbColors;
private float mThumbPosition;
@@ -60,6 +61,15 @@ public class Switch extends View implements Checkable {
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 Switch(Context context) {
super(context);
@@ -85,67 +95,75 @@ public Switch(Context context, AttributeSet attrs, int defStyleAttr, int defStyl
}
private void init(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);
-
- mStrokeSize = a.getDimensionPixelSize(R.styleable.Switch_sw_strokeSize, ThemeUtil.dpToPx(context, 2));
- mStrokeColors = a.getColorStateList(R.styleable.Switch_sw_strokeColor);
- int cap = a.getInteger(R.styleable.Switch_sw_strokeCap, 0);
- if(cap == 0)
- mStrokeCap = Paint.Cap.BUTT;
- else if(cap == 1)
- mStrokeCap = Paint.Cap.ROUND;
- else
- mStrokeCap = Paint.Cap.SQUARE;
- mThumbBorderSize = a.getDimensionPixelSize(R.styleable.Switch_sw_thumbBorderSize, ThemeUtil.dpToPx(context, 2));
- mThumbColors = a.getColorStateList(R.styleable.Switch_sw_thumbColor);
- mThumbRadius = a.getDimensionPixelSize(R.styleable.Switch_sw_thumbRadius, ThemeUtil.dpToPx(context, 8));
- 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();
- mFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
-
- a.recycle();
-
- if(mStrokeColors == 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),
- };
-
- mStrokeColors = 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[]{
- ThemeUtil.colorSwitchThumbNormal(context, 0xFF000000),
- ThemeUtil.colorControlActivated(context, 0xFF000000),
- };
-
- mThumbColors = new ColorStateList(states, colors);
- }
-
- mPaint = new Paint();
- mPaint.setAntiAlias(true);
- mPaint.setStrokeCap(mStrokeCap);
+ mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mDrawRect = new RectF();
mTempRect = new RectF();
- mPath = new Path();
+ mTrackPath = new Path();
+
+ mFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
+
+ applyStyle(context, attrs, defStyleAttr, defStyleRes);
}
+ 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 setOnClickListener(OnClickListener l) {
if(l == mRippleManager)
@@ -190,7 +208,7 @@ public boolean onTouchEvent(@NonNull MotionEvent event) {
mStartTime = SystemClock.uptimeMillis();
break;
case MotionEvent.ACTION_MOVE:
- float offset = (event.getX() - mMemoX) / (mDrawRect.width() - mThumbRadius * 2 - mThumbBorderSize);
+ float offset = (event.getX() - mMemoX) / (mDrawRect.width() - mThumbRadius * 2);
mThumbPosition = Math.min(1f, Math.max(0f, mThumbPosition + offset));
mMemoX = event.getX();
invalidate();
@@ -243,29 +261,29 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
@Override
public int getSuggestedMinimumWidth() {
- return (mThumbRadius * 2 + mThumbBorderSize) * 2 + getPaddingLeft() + getPaddingRight();
+ return mThumbRadius * 4 + Math.max(mShadowSize, getPaddingLeft()) + Math.max(mShadowSize, getPaddingRight());
}
@Override
public int getSuggestedMinimumHeight() {
- return mThumbRadius * 2 + mThumbBorderSize + getPaddingTop() + getPaddingBottom();
+ 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 = getPaddingLeft();
- mDrawRect.right = w - getPaddingRight();
+ mDrawRect.left = Math.max(mShadowSize, getPaddingLeft());
+ mDrawRect.right = w - Math.max(mShadowSize, getPaddingRight());
- int height = mThumbRadius * 2 + mThumbBorderSize;
+ int height = mThumbRadius * 2;
int align = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (align) {
case Gravity.TOP:
- mDrawRect.top = getPaddingTop();
+ mDrawRect.top = Math.max(mShadowSize - mShadowOffset, getPaddingTop());
mDrawRect.bottom = mDrawRect.top + height;
break;
case Gravity.BOTTOM:
- mDrawRect.bottom = h - getPaddingBottom();
+ mDrawRect.bottom = h - Math.max(mShadowSize + mShadowOffset, getPaddingBottom());
mDrawRect.top = mDrawRect.bottom - height;
break;
default:
@@ -275,11 +293,11 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
}
}
- private int getStrokeColor(boolean checked){
+ 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 mStrokeColors.getColorForState(mTempStates, 0);
+ return mTrackColors.getColorForState(mTempStates, 0);
}
private int getThumbColor(boolean checked){
@@ -288,28 +306,57 @@ private int getThumbColor(boolean 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;
- private void getPath(float x, float y, float radius){
- float halfStroke = mStrokeSize / 2f;
-
- mPath.reset();
+ mTrackPath.reset();
- if(mStrokeCap != Paint.Cap.ROUND){
+ 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){
- mPath.moveTo(mDrawRect.left, y - halfStroke);
- mPath.arcTo(mTempRect, 180 + angle, -angle * 2);
- mPath.lineTo(mDrawRect.left, y + halfStroke);
- mPath.close();
+ 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){
- mPath.moveTo(mDrawRect.right, y - halfStroke);
- mPath.arcTo(mTempRect, -angle, angle * 2);
- mPath.lineTo(mDrawRect.right, y + halfStroke);
- mPath.close();
+ mTrackPath.moveTo(mDrawRect.right, y - halfStroke);
+ mTrackPath.arcTo(mTempRect, -angle, angle * 2);
+ mTrackPath.lineTo(mDrawRect.right, y + halfStroke);
+ mTrackPath.close();
}
}
else{
@@ -318,55 +365,51 @@ private void getPath(float x, float y, float radius){
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 + mStrokeSize, y + halfStroke);
- mPath.arcTo(mTempRect, 180 - angle2, angle2 * 2);
+ 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);
- mPath.arcTo(mTempRect, 180 + angle, -angle * 2);
- mPath.close();
+ 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));
- mPath.moveTo((float)(mDrawRect.right - halfStroke + Math.cos(angle2) * halfStroke), (float)(y + Math.sin(angle2) * 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 - mStrokeSize, y - halfStroke, mDrawRect.right, y + halfStroke);
- mPath.arcTo(mTempRect, angle2, -angle2 * 2);
+ 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);
- mPath.arcTo(mTempRect, -angle, angle * 2);
- mPath.close();
+ mTrackPath.arcTo(mTempRect, -angle, angle * 2);
+ mTrackPath.close();
}
}
}
-
- private float getInterpolation(float value){
- return 2 * value * (1f - value);
- }
-
+
@Override
public void draw(@NonNull Canvas canvas) {
super.draw(canvas);
-
- float shinkWidth = getInterpolation(mThumbPosition) * mThumbBorderSize;
-
- float outerRadius = mThumbRadius + mThumbBorderSize / 2f - shinkWidth;
- float x = (mDrawRect.width() - outerRadius * 2) * mThumbPosition + mDrawRect.left + outerRadius;
+
+ float x = (mDrawRect.width() - mThumbRadius * 2) * mThumbPosition + mDrawRect.left + mThumbRadius;
float y = mDrawRect.centerY();
- getPath(x, y, outerRadius);
- mPaint.setColor(ColorUtil.getMiddleColor(getStrokeColor(false), getStrokeColor(true), mThumbPosition));
+ getTrackPath(x, y, mThumbRadius);
+ mPaint.setColor(ColorUtil.getMiddleColor(getTrackColor(false), getTrackColor(true), mThumbPosition));
mPaint.setStyle(Paint.Style.FILL);
- canvas.drawPath(mPath, mPaint);
-
- float strokeWidth = mThumbBorderSize + (mThumbRadius - mThumbBorderSize / 2f) * mThumbPosition - shinkWidth;
- float radius = mThumbRadius - (strokeWidth - mThumbBorderSize) / 2f - shinkWidth;
-
- mPaint.setColor(ColorUtil.getMiddleColor(getThumbColor(false), getThumbColor(true), mThumbPosition));
- mPaint.setStyle(Paint.Style.STROKE);
- mPaint.setStrokeWidth(strokeWidth);
- canvas.drawCircle(x, y, radius, mPaint);
+ 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(){
diff --git a/lib/src/main/res/values/attrs.xml b/lib/src/main/res/values/attrs.xml
index 32539f13..b85da2d0 100644
--- a/lib/src/main/res/values/attrs.xml
+++ b/lib/src/main/res/values/attrs.xml
@@ -150,21 +150,54 @@
-
-
-
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -278,9 +311,11 @@
-
+
+
-
+
+
diff --git a/lib/src/main/res/values/styles.xml b/lib/src/main/res/values/styles.xml
index 6c3caef9..61892644 100644
--- a/lib/src/main/res/values/styles.xml
+++ b/lib/src/main/res/values/styles.xml
@@ -189,14 +189,36 @@
+
+
+
+