I'm fairly new on using ConstraintLayout (java).
What I want to achieve is something like when the numpad is being shown/hidden as a slide animation, I've tried something like this:
Animation a = new Animation() {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
ConstraintLayout.LayoutParams lparams = (ConstraintLayout.LayoutParams) guideline.getLayoutParams();
lparams.guidePercent = 0.5f;
guideline.setLayoutParams(lparams);
}
};
a.setDuration(3000);
a.setFillAfter(true);
guideline.startAnimation(a);
Yes, the guideline (and corresponding views that is attached to it) is being moved to the center of the screen but it is not as smooth as it is supposed to be. Is there a way to achieve the smoothness of the animation?
Any suggestion would be much appreciated!
You can use a ValueAnimator
Sample in Kotlin.
val guideline = findViewById<View>(R.id.guideline2) as Guideline
val end = (guideline.layoutParams as ConstraintLayout.LayoutParams).guidePercent
// get end percent. start at 0
val valueAnimator = ValueAnimator.ofFloat(0f, end)
valueAnimator.duration = 3000
// set duration
valueAnimator.interpolator = AccelerateDecelerateInterpolator()
// set interpolator and updateListener to get the animated value
valueAnimator.addUpdateListener { valueAnimator ->
val lp = guideline.layoutParams as ConstraintLayout.LayoutParams
// get the float value
lp.guidePercent = valueAnimator.animatedValue as Float
// update layout params
guideline.layoutParams = lp
}
valueAnimator.start()
Java Version
final Guideline guideline = findViewById(R.id.guideline2) ;
ConstraintLayout.LayoutParams lp = (ConstraintLayout.LayoutParams)guideline.getLayoutParams();
float end =lp.guidePercent;
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, end);
valueAnimator.setDuration(3000);
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
ConstraintLayout.LayoutParams lp = (ConstraintLayout.LayoutParams)guideline.getLayoutParams();
lp.guidePercent = valueAnimator.getAnimatedFraction();
guideline.setLayoutParams(lp);
}
});
Try animating your layout with ConstraintSet. See Beautiful animations using Android ConstraintLayout.
[T]here is one other benefit of ConstraintLayout that most people are unaware of and the official documentation curiously doesn’t mention anything about: performing cool animations on your ConstraintLayout views with very little code.
There are other sources and some videos on this technique. I think that you will find it a smoother way to do what you are trying to do.
Related
I have a custom toolbar that's built without a xml, all the code is just written in java. I am using the setLogo() method to add a drawable but the image's default gravity is to the left. How can I change its position/gravity so that it's in the middle?
I was trying to override the setLogo() method but I can't apply a LayoutParams to a drawable.
This is what I am talking about:
#Override
public void setLogo(#DrawableRes int resId) {
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
float factor = getContext().getResources().getDisplayMetrics().density;
int margin = (int) (16 * factor);
lp.setMargins(margin, 0, margin, 0);
lp.gravity = Gravity.CENTER;
resId.setLayoutParams(lp); // this does not work
setLogo(AppCompatResources.getDrawable(getContext(), resId));
}
What's the right way to achieve this without a xml to refer to?
Thanks!
I'm writing a simple app that implements a material design compliant nav drawer as described by this Stack post: https://stackoverflow.com/a/27153313/1621380
The ScrimInsetsFrameLayout works great and everything is dandy until I attempt to programmatically change the color of the status bar. My app uses the Palette API to change the toolbar and status bar color dynamically.
I'm using the property animation api to animate the toolbar and it works great! but I try to do the same animation on the statusbar and it doesn't seem to want to animate. Here's an example
Here is the code for my animator:
public static void fadeStatusBar(final DrawerLayout layout, Integer from, Integer to) {
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), from, to);
colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animator) {
layout.setStatusBarBackgroundColor((Integer) animator.getAnimatedValue());
}
});
colorAnimation.start();
}
}
Note: That same code is fading the toolbar, so its proven to work.
My question is; does anyone know of a way to get a smooth transition when using DrawerLayout.setStatusBarBackgorundColour()?
Note: I have used the Window method window.setStatusBarColor() method, and it animates fine but breaks the "transparent statusbar" when the NavDrawer is pulled in.
As advised #adneal's comment on my original question the answer was to invalidate the view during the animation.
DrawerLayout calculates the top inset and draws the status bar background itself. So, you just need to make a call to View.invalidate to force it to redraw while the animation is being updated. #adneal
I've updated my code to this
ValueAnimator colorAnimation = ValueAnimator.ofArgb(from, to);
colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animator) {
layout.invalidate();
layout.setStatusBarBackgroundColor((Integer) animator.getAnimatedValue());
}
});
colorAnimation.start();
For anyone wondering why this worked, I found this stack post enlightening: When it's necessary to execute invalidate() on a View?
you can do this Easily like below:
1 - set your statusbar color to transparent by using this:
<item name="colorPrimaryDark">#android:color/transparent</item>
2 - define application's view to change it background:
View root = *A_view_on_the_activity*.getRootView();
3 - then add this function to your project:
private void color_change(final View view,int from_color,int to_color){
final float[] from = new float[3],
to = new float[3];
String hexColor_from = String.format("#%06X", (0xFFFFFF & from_color));
String hexColor_to = String.format("#%06X", (0xFFFFFF & to_color));
Color.colorToHSV(Color.parseColor(hexColor_from), from); // from
Color.colorToHSV(Color.parseColor(hexColor_to), to); // to
ValueAnimator anim = ValueAnimator.ofFloat(0, 1); // animate from 0 to 1
anim.setDuration(600); // for 300 ms
final float[] hsv = new float[3]; // transition color
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
#Override public void onAnimationUpdate(ValueAnimator animation) {
// Transition along each axis of HSV (hue, saturation, value)
hsv[0] = from[0] + (to[0] - from[0])*animation.getAnimatedFraction();
hsv[1] = from[1] + (to[1] - from[1])*animation.getAnimatedFraction();
hsv[2] = from[2] + (to[2] - from[2])*animation.getAnimatedFraction();
view.setBackgroundColor(Color.HSVToColor(hsv));
}
});
anim.start();
}
4 - and then change the hole application's background color animation by using the function that Written above:
color_change(root, from_color, to_color);
I have a button that I want to put on a 45 degree angle. For some reason I can't get this to work.
Can someone please provide the code to accomplish this?
API 11 added a setRotation() method to all views.
You could create an animation and apply it to your button view. For example:
// Locate view
ImageView diskView = (ImageView) findViewById(R.id.imageView3);
// Create an animation instance
Animation an = new RotateAnimation(0.0f, 360.0f, pivotX, pivotY);
// Set the animation's parameters
an.setDuration(10000); // duration in ms
an.setRepeatCount(0); // -1 = infinite repeated
an.setRepeatMode(Animation.REVERSE); // reverses each repeat
an.setFillAfter(true); // keep rotation after animation
// Aply animation to image view
diskView.setAnimation(an);
Extend the TextView class and override the onDraw() method. Make sure the parent view is large enough to handle the rotated button without clipping it.
#Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.rotate(45,<appropriate x pivot value>,<appropriate y pivot value>);
super.onDraw(canvas);
canvas.restore();
}
I just used the simple line in my code and it works :
myCusstomView.setRotation(45);
Hope it works for you.
One line in XML
<View
android:rotation="45"
... />
Applying a rotation animation (without duration, thus no animation effect) is a simpler solution than either calling View.setRotation() or override View.onDraw method.
// substitude deltaDegrees for whatever you want
RotateAnimation rotate = new RotateAnimation(0f, deltaDegrees,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
// prevents View from restoring to original direction.
rotate.setFillAfter(true);
someButton.startAnimation(rotate);
Rotating view with rotate() will not affect your view's measured size. As result, rotated view be clipped or not fit into the parent layout. This library fixes it though:
https://github.com/rongi/rotate-layout
Joininig #Rudi's and #Pete's answers. I have created an RotateAnimation that keeps buttons functionality also after rotation.
setRotation() method preserves buttons functionality.
Code Sample:
Animation an = new RotateAnimation(0.0f, 180.0f, mainLayout.getWidth()/2, mainLayout.getHeight()/2);
an.setDuration(1000);
an.setRepeatCount(0);
an.setFillAfter(false); // DO NOT keep rotation after animation
an.setFillEnabled(true); // Make smooth ending of Animation
an.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {}
#Override
public void onAnimationRepeat(Animation animation) {}
#Override
public void onAnimationEnd(Animation animation) {
mainLayout.setRotation(180.0f); // Make instant rotation when Animation is finished
}
});
mainLayout.startAnimation(an);
mainLayout is a (LinearLayout) field
As mentioned before, the easiest way it to use rotation available since API 11:
android:rotation="90" // in XML layout
view.rotation = 90f // programatically
You can also change pivot of rotation, which is by default set to center of the view. This needs to be changed programatically:
// top left
view.pivotX = 0f
view.pivotY = 0f
// bottom right
view.pivotX = width.toFloat()
view.pivotY = height.toFloat()
...
In Activity's onCreate() or Fragment's onCreateView(...) width and height are equal to 0, because the view wasn't measured yet. You can access it simply by using doOnPreDraw extension from Android KTX, i.e.:
view.apply {
doOnPreDraw {
pivotX = width.toFloat()
pivotY = height.toFloat()
}
}
if you wish to make it dynamically with an animation:
view.animate()
.rotation(180)
.start();
THATS IT
#Ichorus's answer is correct for views, but if you want to draw rotated rectangles or text, you can do the following in your onDraw (or onDispatchDraw) callback for your view:
(note that theta is the angle from the x axis of the desired rotation, pivot is the Point that represents the point around which we want the rectangle to rotate, and horizontalRect is the rect's position "before" it was rotated)
canvas.save();
canvas.rotate(theta, pivot.x, pivot.y);
canvas.drawRect(horizontalRect, paint);
canvas.restore();
fun rotateArrow(view: View): Boolean {
return if (view.rotation == 0F) {
view.animate().setDuration(200).rotation(180F)
true
} else {
view.animate().setDuration(200).rotation(0F)
false
}
}
That's simple,
in Java
your_component.setRotation(15);
or
your_component.setRotation(295.18f);
in XML
<Button android:rotation="15" />
I'm writing an android view (Android 12).
I have a linearlayout with editText controls on it.
I want to change the linearlayout background image when the soft keyboard is out and change it again when the keyboard is hidden.
I have tried to set a focus listener on each editText, but it won't help.
How can I achieve this?
First, add an id to your layout:
android:id="#+id/view"
So for example:
<LinearLayout
android:id="#+id/view"
android:layout_width="match_parent"
android:layout_height="match_parent" >
Then use this code from this question to determine if the soft keyboard is visible. You should probably put this in your onCreate method.
final View root = findViewById(R.id.view);
root.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int heightDiff = root.getRootView().getHeight() - root.getHeight();
if (heightDiff > 100) { // more than 100 pixels is probably a keyboard
// keyboard is shown
layout.setBackground(getResources().getDrawable(R.drawable.idOfPic));
} else {
// keyboard is not shown
layout.setBackground(getResources().getDrawable(R.drawable.otherPic));
}
}
});
Note depending on your layout (speaking from my own experience), the if (heightDiff > 100) may have to change. It might be if (heightDiff > 150) or something else; the pixel height is arbitrary.
Unfortunately, there is no real way to determine if the soft keyboard is visible (ridiculous). This is the best way it can be done.
try this:
final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Rect r = new Rect();
//r will be populated with the coordinates of your view that area still visible.
activityRootView.getWindowVisibleDisplayFrame(r);
int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
heightDiff = convertPixelsToDp(heightDiff , this);
if (heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...
... do something here
}
}
});
more info in this link and this
for working in all device change heightDiff to dp, and work with that and for changing that use following method:
public static float convertPixelsToDp(float px, Context context){
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float dp = px / (metrics.densityDpi / 160f);
return dp;
}
I use NineOldAndroids library to scale my custom layout.
public class MyLayout extends FrameLayout {
// LayoutParams.MATCH_PARENT and all.
...
#Override
public boolean setPositionAndScale(ViewGroup v, PositionAndScale pas, PointInfo pi) {
...
mScale = pas.getScale();
ViewHelper.setScaleX(this, mScale);
ViewHelper.setScaleY(this, mScale);
}
}
I have tried FrameLayout and AbsoluteLayout. All have the same effect.
When mScale < 1.0 scaling/zooming works but part of the layout is clipped.
mScale = 1.0:
mScale < 1.0: scaling/zooming works but layout is clipped
How can i fix this issue?
Edit: The picture was taken on ICS. So I don't think it's NineOldAndroids problem.
The parent of your view must have the property android:clipChildren disabled (from layout file or with setClipChildren(false) ).
But with this method you don't get the touch events outside the view clip bounds. You can work around by sending them from your activity or writing a custom ViewGroup parent.
I'm using a different hack which seems to work in my case, the trick is to maintain your own transformation matrix. Then, you have to overload a lot of ViewGroup's method to make it work. For example :
#Override
protected void dispatchDraw(Canvas canvas) {
Log.d(TAG, "dispatchDraw " + canvas);
canvas.save();
canvas.concat(mMatrix);
super.dispatchDraw(canvas);
canvas.restore();
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG, "dispatchTouchEvent " + ev);
ev.transform(getInvMatrix()); //
return super.dispatchTouchEvent(ev);
}
private Matrix getInvMatrix()
{
if(!mTmpMatIsInvMat)
mMatrix.invert(mTmpMat);
mTmpMatIsInvMat = true;
return mTmpMat;
}
In case anyone got in to the same situation as me.
I ended up using this approach:
protected void setScale(float scale, boolean updateView) {
mScale = scale;
if (updateView) {
LayoutParams params = getLayoutParams();
onUpdateScale(scale, params);
setLayoutParams(params);
}
}
protected void onUpdateScale(float scale, LayoutParams params) {
params.leftMargin = (int) (mModel.getX() * scale);
params.topMargin = (int) (mModel.getY() * scale);
params.width = (int) (mModel.getWidth() * scale);
params.height = (int) (mModel.getHeight() * scale);
}
Since API Level 11, the View class has setScaleX() and setScaleY() methods, that work as expected and also scale sub-views of the scaled view. So, if that'd be a way for you, drop the library and just do
v.setScaleX(mScale);
v.setScaleY(mScale);
If I understand your problem correctly, you are scaling a view group and expect the included views to scale accordingly. It doesn't work that way: you scale the view group and it changes size, but its children views do not.
Just scale all subviews. Even so, I am not sure that texts and images are going to be automatically scaled. What you want is zoom, not scale. Try this reference.
Use ViewGroup.layout. It may be the easiest way to scale(&move) ViewGroup.