ObjectAnimator.ofFloat(Object target, String xPropertyName, String yPropertyName, Path path) seems to work in API 21 but not 23?
Simple reproduction: Here's the activity and layout. Try long press on the text view, both level will work. But, just tap on the view: 21 works but not 23.
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View hello = findViewById(R.id.hello);
final Path p = new Path();
p.addCircle(-200, 0, 100, Path.Direction.CW);
hello.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ObjectAnimator a = ObjectAnimator.ofFloat(hello, "translationX", "translationY", p);
a.setDuration(1000);
a.start();
}
});
hello.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
ObjectAnimator a = ObjectAnimator.ofFloat(hello, "scaleX", 2);
a.setDuration(1000);
a.start();
return true;
}
});
}
}
--
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="letstwinkle.com.pathtest.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginEnd="63dp"
android:layout_marginTop="210dp"
android:id="#+id/hello"
android:background="#880033"/>
</RelativeLayout>
This may be the same as this bug reported in June. https://code.google.com/p/android/issues/detail?id=212776
As a workaround, you can use a ValueAnimator with a custom AnimatorUpdateListener.
First you need a PathMeasure for your Path. This will give you access to data like path length and coordinates on the path for a specific distance (0 <= distance <= path length).
Path p = new Path();
p.addCircle(-200, 0, 100, Path.Direction.CW);
PathMeasure pathMeasure = new PathMeasure(p, true);
Then you set the ValueAnimator to animate from 0 to the path length.
final ValueAnimator a = ValueAnimator.ofFloat(0,pathMeasure.getLength());
a.setDuration(1000);
Next, you add an AnimatorUpdateListener to actually manipulate the View. I'll show first how to add it:
MyAnimatorUpdateListener myListener = new MyAnimatorUpdateListener(hello, pathMeasure);
a.addUpdateListener(myListener);
Continue with the OnClickListener:
hello.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
a.start();
}
});
Please note that I changed the code and declared/ initialized the ValueAnimator outside of the OnClickListener to get a better performance: this way, it will not be newly created each time I click the TextView and so the garbage collector will be less busy :).
Finally, the AnimatorUpdateListener code:
class MyAnimatorUpdateListener implements ValueAnimator.AnimatorUpdateListener
{
private View view;
private PathMeasure pathMeasure;
private float[] coordinates = new float[2];
public MyAnimatorUpdateListener(View view, PathMeasure pathMeasure)
{
this.view = view;
this.pathMeasure = pathMeasure;
}
#Override
public void onAnimationUpdate(ValueAnimator animation) {
float distance = (float) animation.getAnimatedValue();
pathMeasure.getPosTan(distance, coordinates, null);
view.setTranslationX(coordinates[0]);
view.setTranslationY(coordinates[1]);
}
}
Related
public class MainActivity extends Activity {
ValueAnimator animator;
ImageButton testImageButton;
float rad=1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testImageButton = findViewById(R.id.ib_test);
testImageButton.setOutlineProvider(outlineProvider);
testImageButton.setClipToOutline(true);
animator = (ValueAnimator)
AnimatorInflater.loadAnimator(this,R.animator.radius);
animator.addUpdateListener(mListener);
}
public void clickedAnimator(View v){
animator.start();
}
public void testMe(View v){
l("testMe");
}
ValueAnimator.AnimatorUpdateListener mListener = new
ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float i= (float) valueAnimator.getAnimatedValue();
rad = i;
testImageButton.setOutlineProvider(outlineProvider);
}
};
ViewOutlineProvider outlineProvider = new ViewOutlineProvider() {
#Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(0,0,view.getWidth(),view.getHeight(),
rad*((float)view.getWidth())/2);
}
};
public void l(String s){
Log.v(getClass().getSimpleName(),s);
}
}
I am trying to animate the outline of an ImageView from Circle to Rectangle by constantly updating the ViewOutlineProvider that is set to the imageview.
Everything works fine but when the animation ends, the Image in the imageView Blinks once.
The value of rad changes from 1 to 0 in 500ms.
Hmmm, this line might help you: android:fillAfter="true"
Place this in your animation XML file inside the set attribute like:
<set [...]
android:fillAfter="true">
This helped me on a number of occasions when the animation was bugging at the end. Might work for you, might not, it's worth trying.
Check attached image for easy explanation.
Translate animation works but it animates inside the same view.
I want view to fly out from one layout to other.
I tried this from another answer here. (Animates in same layout)
public class Animations {
public Animation fromAtoB(float fromX, float fromY, float toX, float toY, int speed){
Animation fromAtoB = new TranslateAnimation(
Animation.ABSOLUTE, //from xType
fromX,
Animation.ABSOLUTE, //to xType
toX,
Animation.ABSOLUTE, //from yType
fromY,
Animation.ABSOLUTE, //to yType
toY
);
fromAtoB.setDuration(speed);
fromAtoB.setInterpolator(new AnticipateOvershootInterpolator(1.0f));
return fromAtoB;
}
I recently did animation of a similar kind using Animators. In general, views will not display themselves outside of their parents' boundaries, the view will be cut by it's parent's boundaries. That's why, the trick is to place a new view (shuttleView) on top of the origin view (fromView) that you want to animate, align them, and animate scaling/translation of shuttleView into a target view (toView).
This solution supports both scaling and translation, here is sample: https://www.dropbox.com/s/iom95o93076h52f/device-2016-06-03-111557.mp4?dl=0
Here is the code:
activity_main.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="90dp"
android:layout_alignParentTop="true"
android:background="#android:color/holo_blue_dark">
<TextView
android:id="#+id/itemTo"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="10dp"
android:background="#android:color/holo_blue_bright"
android:text="to"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="90dp"
android:layout_alignParentBottom="true"
android:background="#android:color/holo_blue_dark">
<TextView
android:layout_width="90dp"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:background="#android:color/holo_blue_bright" />
<TextView
android:id="#+id/itemFrom"
android:layout_width="90dp"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:text="from"
android:background="#android:color/holo_blue_bright" />
<TextView
android:layout_width="90dp"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:background="#android:color/holo_blue_bright" />
</LinearLayout>
<View
android:id="#+id/shuttle"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#android:color/holo_blue_bright"/>
Activity class:
public class MainActivity extends AppCompatActivity {
public static final int ANIMATION_SPEED = 3000;
private RelativeLayout rootView;
private View fromView, toView, shuttleView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rootView = (RelativeLayout) findViewById(R.id.rootView);
fromView = findViewById(R.id.itemFrom);
toView = findViewById(R.id.itemTo);
shuttleView = findViewById(R.id.shuttle);
fromView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Rect fromRect = new Rect();
Rect toRect = new Rect();
fromView.getGlobalVisibleRect(fromRect);
toView.getGlobalVisibleRect(toRect);
AnimatorSet animatorSet = getViewToViewScalingAnimator(rootView, shuttleView, fromRect, toRect, ANIMATION_SPEED, 0);
animatorSet.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
shuttleView.setVisibility(View.VISIBLE);
fromView.setVisibility(View.INVISIBLE);
}
#Override
public void onAnimationEnd(Animator animation) {
shuttleView.setVisibility(View.GONE);
fromView.setVisibility(View.VISIBLE);
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
animatorSet.start();
}
});
}
public static AnimatorSet getViewToViewScalingAnimator(final RelativeLayout parentView,
final View viewToAnimate,
final Rect fromViewRect,
final Rect toViewRect,
final long duration,
final long startDelay) {
// get all coordinates at once
final Rect parentViewRect = new Rect(), viewToAnimateRect = new Rect();
parentView.getGlobalVisibleRect(parentViewRect);
viewToAnimate.getGlobalVisibleRect(viewToAnimateRect);
viewToAnimate.setScaleX(1f);
viewToAnimate.setScaleY(1f);
// rescaling of the object on X-axis
final ValueAnimator valueAnimatorWidth = ValueAnimator.ofInt(fromViewRect.width(), toViewRect.width());
valueAnimatorWidth.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
// Get animated width value update
int newWidth = (int) valueAnimatorWidth.getAnimatedValue();
// Get and update LayoutParams of the animated view
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) viewToAnimate.getLayoutParams();
lp.width = newWidth;
viewToAnimate.setLayoutParams(lp);
}
});
// rescaling of the object on Y-axis
final ValueAnimator valueAnimatorHeight = ValueAnimator.ofInt(fromViewRect.height(), toViewRect.height());
valueAnimatorHeight.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
// Get animated width value update
int newHeight = (int) valueAnimatorHeight.getAnimatedValue();
// Get and update LayoutParams of the animated view
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) viewToAnimate.getLayoutParams();
lp.height = newHeight;
viewToAnimate.setLayoutParams(lp);
}
});
// moving of the object on X-axis
ObjectAnimator translateAnimatorX = ObjectAnimator.ofFloat(viewToAnimate, "X", fromViewRect.left - parentViewRect.left, toViewRect.left - parentViewRect.left);
// moving of the object on Y-axis
ObjectAnimator translateAnimatorY = ObjectAnimator.ofFloat(viewToAnimate, "Y", fromViewRect.top - parentViewRect.top, toViewRect.top - parentViewRect.top);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setInterpolator(new DecelerateInterpolator(1f));
animatorSet.setDuration(duration); // can be decoupled for each animator separately
animatorSet.setStartDelay(startDelay); // can be decoupled for each animator separately
animatorSet.playTogether(valueAnimatorWidth, valueAnimatorHeight, translateAnimatorX, translateAnimatorY);
return animatorSet;
}
}
You can do a whole bunch of customizations in terms of what appears and disappears at different stages of animation in animatorSet listener. Hope it's helpful.
If someone is looking for simpler solution, you can use transitions framework:
https://developer.android.com/training/transitions/index.html
To animate translation from one parent view to another, there is special transition ChangeTransform:
https://developer.android.com/reference/android/transition/ChangeTransform.html
And a small example:
animatedView = View.inflate(ActivityMain.this, R.layout.item, firstParent);
animatedView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
Transition move = new ChangeTransform()
.addTarget(animatedView)
.setDuration(2000));
TransitionManager.beginDelayedTransition(rootParent, move);
firstParent.removeView(animatedView);
animatedView.setPadding(2,2,2,2);
animatedView.setElevation(4);
secondParent.addView(animatedView, 0);
I'm trying to go around different functionality integrated with Android Material Design but I can't to do this type of animation when a view fill another like that :
Do you know how to do it or a library/project an example that does this?
I tried to implement this below API 21
add gradle dependancy
dependencies {
compile 'com.github.ozodrukh:CircularReveal:1.0.6#aar'
}
My activity xml is
activity_reval_anim.xml
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".RevalAnimActivity">
<ImageView
android:id="#+id/img_top"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:background="#color/color_primary"
android:src="#drawable/ala"/>
<io.codetail.widget.RevealLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_below="#+id/img_top"
android:background="#color/color_primary">
<LinearLayout
android:visibility="invisible"
android:id="#+id/ll_reveal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/color_accent"
android:orientation="horizontal"
></LinearLayout>
</io.codetail.widget.RevealLinearLayout>
<ImageButton
android:id="#+id/img_floating_btn"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_alignParentRight="true"
android:layout_marginRight="40dp"
android:layout_marginTop="170dp"
android:background="#drawable/expand_btn"/>
</RelativeLayout>
My Activity java is
RevalAnimActivity.java
public class RevalAnimActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_reval_anim);
final ImageButton mFloatingButton = (ImageButton) findViewById(R.id.img_floating_btn);
mFloatingButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
animateButton(mFloatingButton);
}
});
}
private void animateButton(final ImageButton mFloatingButton) {
mFloatingButton.animate().translationXBy(0.5f).translationY(150).translationXBy(-0.9f)
.translationX(-150). setDuration(300).setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
animateReavel((int) mFloatingButton.getX(), 150,mFloatingButton);
}
});
}
private void animateReavel(int cx, int cy, final ImageButton mFloatingButton) {
final View myView = findViewById(R.id.ll_reveal);
// get the final radius for the clipping circle
float finalRadius = hypo(myView.getWidth(), myView.getHeight());
SupportAnimator animator =
ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
animator.addListener(new SupportAnimator.AnimatorListener() {
#Override
public void onAnimationStart() {
mFloatingButton.setVisibility(View.INVISIBLE);
myView.setVisibility(View.VISIBLE);
}
#Override
public void onAnimationEnd() {
Toast.makeText(getApplicationContext(), "Done", Toast.LENGTH_LONG)
.show();
}
#Override
public void onAnimationCancel() {
}
#Override
public void onAnimationRepeat() {
}
});
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.setDuration(1000);
animator.start();
}
static float hypo(int a, int b) {
return (float) Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
}
}
The solution to do that is pathInterpolator and the name of this effect is Curved Motion.
Animations in material design rely on curves for time interpolation
and spatial movement patterns. With Android 5.0 (API level 21) and
above, you can define custom timing curves and curved motion patterns
for animations.
You can see how to implement it here :
http://developer.android.com/training/material/animations.html#CurvedMotion
And sample on GitHub HERE :
Need to make next animation (on android 2.2 and above):
1.Moving button from top to bottom (after clicking on him),
2.Moving back from bottom to top (After clicking on him again).
First animation works fine, but the second not, the btn "jumps" from bottom to top and not animate.
Code:
public class MainActivity extends Activity {
static RelativeLayout relativeLayout;
static Button btn;
static Boolean isUp = true;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.button1);
relativeLayout = (RelativeLayout) findViewById(R.id.relative_layout);
btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if(isUp){
isUp = false;
v.startAnimation(MainActivity.getVerticalSlideAnimation(0,relativeLayout.getBottom() - v.getHeight(),500,0));
}else{
isUp = true;
v.startAnimation(MainActivity.getVerticalSlideAnimation(relativeLayout.getBottom() - v.getHeight(),0,500,0));
}
}
});
}
public static Animation getVerticalSlideAnimation(int fromYPosition, final int toYPosition, int duration, int startOffset)
{
TranslateAnimation translateAnimation = new TranslateAnimation(1, 0.0F, 1, 0.0F, 0, fromYPosition, 0, toYPosition);
translateAnimation.setDuration(duration);
translateAnimation.setInterpolator(new AccelerateInterpolator());
translateAnimation.setStartOffset(startOffset);
//Stop animation after finishing.
//translateAnimation.setFillAfter(true);
translateAnimation.setAnimationListener(new AnimationListener()
{
public void onAnimationStart(Animation animation) { }
public void onAnimationRepeat(Animation animation) { }
public void onAnimationEnd(Animation animation) {
btn.setY(toYPosition);
}
});
return translateAnimation;
}
}
Layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/relative_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:text="Button" />
</RelativeLayout>
Ok, I solved it.
There are few issuses you should know about animation:
The animation paremeters are not simple "From (fixed position)" --> "To (fix position)" as you should think. There are more like "From (current position/0)" --> "How much steps to do and on which direction (pluse for positive/ minus for negative)"
The Animation doesn't change the real position of the view on the screen, Therefore if you want to stop the animation at the end position, you should use:
animation.setFillAfter(true);
If you do want to change the REAL position of the view you should update the view parameters on "onAnimationEnd" (like below code), or calculate position and set Y/X position manually (again on "onAnimationEnd"), like:
animatedView.setY(stopPosition);
The Code:
public class AnimationActivity extends Activity {
private boolean isUp;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
((Button) findViewById(R.id.button1))
.setOnClickListener(new OnClickListener() {
public void onClick(final View v) {
final float direction = (isUp) ? -1 : 1;
final float yDelta = getScreenHeight() - (2 * v.getHeight());
final int layoutTopOrBottomRule = (isUp) ? RelativeLayout.ALIGN_PARENT_TOP : RelativeLayout.ALIGN_PARENT_BOTTOM;
final Animation animation = new TranslateAnimation(0,0,0, yDelta * direction);
animation.setDuration(500);
animation.setAnimationListener(new AnimationListener() {
public void onAnimationStart(Animation animation) {
}
public void onAnimationRepeat(Animation animation) {
}
public void onAnimationEnd(Animation animation) {
// fix flicking
// Source : http://stackoverflow.com/questions/9387711/android-animation-flicker
TranslateAnimation anim = new TranslateAnimation(0.0f, 0.0f, 0.0f, 0.0f);
anim.setDuration(1);
v.startAnimation(anim);
//set new params
LayoutParams params = new LayoutParams(v.getLayoutParams());
params.addRule(RelativeLayout.CENTER_HORIZONTAL);
params.addRule(layoutTopOrBottomRule);
v.setLayoutParams(params);
}
});
v.startAnimation(animation);
//reverse direction
isUp = !isUp;
}
});
}
private float getScreenHeight() {
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
return (float) displaymetrics.heightPixels;
}
}
public class AnimationActivity extends Activity {
private boolean isUp;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
((Button) findViewById(R.id.button1))
.setOnClickListener(new OnClickListener() {
public void onClick(final View v) {
final float direction = (isUp) ? -1 : 1;
final float yDelta = getScreenHeight() - (2 * v.getHeight());
final int layoutTopOrBottomRule = (isUp) ? RelativeLayout.ALIGN_PARENT_TOP : RelativeLayout.ALIGN_PARENT_BOTTOM;
final Animation animation = new TranslateAnimation(0,0,0, yDelta * direction);
animation.setDuration(500);
animation.setAnimationListener(new AnimationListener() {
public void onAnimationStart(Animation animation) {
}
public void onAnimationRepeat(Animation animation) {
}
public void onAnimationEnd(Animation animation) {
// fix flicking
// Source : http://stackoverflow.com/questions/9387711/android-animation-flicker
TranslateAnimation anim = new TranslateAnimation(0.0f, 0.0f, 0.0f, 0.0f);
anim.setDuration(1);
v.startAnimation(anim);
//set new params
LayoutParams params = new LayoutParams(v.getLayoutParams());
params.addRule(RelativeLayout.CENTER_HORIZONTAL);
params.addRule(layoutTopOrBottomRule);
v.setLayoutParams(params);
}
});
v.startAnimation(animation);
//reverse direction
isUp = !isUp;
}
});
}
private float getScreenHeight() {
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
return (float) displaymetrics.heightPixels;
}
Is it possible to have a switch widget for ICS versions, but a checkbox for pre ICS? If so, how?
I'm not worried about other components, only switch.
MY SOLUTION
Seeing as switch and checkbox both inherit from CompoundButton, I just did this
((CompoundButton)findViewById(R.id.swTax)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
calculateValues();
}
});
Create a separate layout XML file for ICS versions by placing it in a separate layout folder, e.g. layout-v14. To prevent a lot of duplicate XML, use one main layout file and include the widget. The resulting file structure would look something like this:
layout
mylayout.xml
widget.xml
layout-v14
widget.xml
In mylayout.xml you would have something like:
<include layout="#layout/widget" />
In the Activity for this layout you will have to check the version as well before setting up any interaction with either the CheckBox or Switch widget:
int version = android.os.Build.VERSION.SDK_INT;
if (version >= 14) {
// get your Switch view and set up listeners etc
}
else {
// get your CheckBox view and set up listeners etc
}
I've tried every solution that i found but non of them were fit my needs, so i created my own widget wich is used ObjectAnimator from nineOld compatibility lib and works pretty fine on any android API.
import android.widget.RelativeLayout;
import com.myapp.utilities.AppUtils;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorListenerAdapter;
import com.nineoldandroids.animation.ObjectAnimator;
public class SwitchButton extends RelativeLayout {
public static final int TEXT_SIZE = 11;
public float HANDLE_SHIFT = -40f;
public float TEXT_RIGHT_SHIFT = 40f;
public static int BUTTON_ID = 0x00009999;
public static int TEXT_ID = 0x00008888;
private Button handleButton;
private RoboTextView textView;
private boolean switchEnabled;
private String yesStr;
private String noStr;
private int TEXT_LEFT_PADDING = 13;
private ObjectAnimator animateHandleLeftShift;
private ObjectAnimator animateHandleRightShift;
private int HANDLE_BUTTON_HEIGHT = 22;
private int HANDLE_BUTTON_WIDTH = 42;
private ObjectAnimator animateTextLeftShift;
private ObjectAnimator animateTextRightShift;
public SwitchButton(Context context) {
super(context);
onCreate(context);
}
private void onCreate(Context context) {
float density = context.getResources().getDisplayMetrics().density;
TEXT_LEFT_PADDING *= density;
HANDLE_BUTTON_HEIGHT *= density;
HANDLE_BUTTON_WIDTH *= density;
HANDLE_SHIFT *= density;
TEXT_RIGHT_SHIFT *= density;
yesStr = getContext().getString(R.string.yes).toUpperCase();
noStr = getContext().getString(R.string.no).toUpperCase();
{// Button
handleButton = new Button(getContext());
RelativeLayout.LayoutParams buttonParams = new LayoutParams(HANDLE_BUTTON_WIDTH, HANDLE_BUTTON_HEIGHT);
buttonParams.addRule(RelativeLayout.CENTER_VERTICAL);
buttonParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
handleButton.setBackgroundResource(R.drawable.button_switch_handle_selector);
handleButton.setId(BUTTON_ID);
addView(handleButton, buttonParams);
}
{// Text
textView = new RoboTextView(getContext());
LayoutParams textParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
textParams.addRule(RelativeLayout.CENTER_VERTICAL);
textView.setText(yesStr);
textView.setTextColor(getContext().getResources().getColor(R.color.new_normal_gray));
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, TEXT_SIZE);
textView.setPadding(TEXT_LEFT_PADDING, 0, 0, 0);
textView.setFont(RoboTextView.ROBOTO_BOLD_FONT);
textView.setId(TEXT_ID);
float shadowRadius = 0.5f ;
float shadowDx = 0;
float shadowDy = 1;
textView.setShadowLayer(shadowRadius, shadowDx, shadowDy, Color.BLACK);
addView(textView, textParams);
}
initFlipAnimation();
}
#Override
public void setOnClickListener(OnClickListener l) {
handleButton.setOnClickListener(l);
textView.setOnClickListener(l);
}
public void toggle(View view){
if (AppUtils.HONEYCOMB_PLUS_API && view.getId() == TEXT_ID) { // ignore text clicks
return;
}
switchEnabled = !switchEnabled;
if (switchEnabled) {
// animate handle to the left
animateHandleLeftShift.start();
animateTextLeftShift.start();
textView.setText(noStr);
} else {
animateHandleRightShift.start();
animateTextRightShift.start();
textView.setText(yesStr);
}
}
private android.view.animation.Interpolator accelerator = new LinearInterpolator();
private static final int DURATION = 70;
private void initFlipAnimation() {
animateHandleLeftShift = ObjectAnimator.ofFloat(handleButton, "translationX", 0f, HANDLE_SHIFT);
animateHandleLeftShift.setDuration(DURATION);
animateHandleLeftShift.setInterpolator(accelerator);
animateHandleRightShift = ObjectAnimator.ofFloat(handleButton, "translationX", HANDLE_SHIFT, 0f);
animateHandleRightShift.setDuration(DURATION);
animateHandleRightShift.setInterpolator(accelerator);
animateHandleLeftShift.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator anim) {
// TODO
}
});
animateTextLeftShift = ObjectAnimator.ofFloat(textView, "translationX", 0f, TEXT_RIGHT_SHIFT);
animateTextLeftShift.setDuration(DURATION);
animateTextLeftShift.setInterpolator(accelerator);
animateTextRightShift = ObjectAnimator.ofFloat(textView, "translationX", TEXT_RIGHT_SHIFT, 0f);
animateTextRightShift.setDuration(DURATION);
animateTextRightShift.setInterpolator(accelerator);
}
}
In XML
<com.chess.SwitchButton
android:id="#+id/ratedGameSwitch"
android:layout_width="#dimen/button_switch_width"
android:layout_height="#dimen/button_switch_height"
android:background="#drawable/button_switch_back"
/>
In the Activity/Fragment you only have to findViewById and set clickListener to it, and in onClick callback handle it:
switchButton = (SwitchButton) optionsView.findViewById(R.id.ratedGameSwitch);
switchButton.setOnClickListener(this);
#Override
public void onClick(View view) {
if (view.getId() == SwitchButton.BUTTON_ID || view.getId() == SwitchButton.TEXT_ID){
switchButton.toggle(view);
}
}
I use a iOS like Segmented control (extension of radio button) with on/off instead of a switch then you can use the same code for old and new SDK.
There is a nice sample project with all the code here:
https://github.com/makeramen/android-segmentedradiobutton
It has both text and graphic samples.
It's happened!
http://developer.android.com/tools/support-library/index.html
Changes for v7 appcompat library:
Added SwitchCompat, a backport of the Switch widget that was added in Android 4.0 (API level 14).