I have a Relative Layout within an XML file with an ImageView element which contains both width and height for an image.
<?xml version="1.0" encoding="utf-8"?>
<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:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/tankHeight"
android:layout_width="210dp"
android:layout_height="350dp"
android:layout_centerInParent="true"
app:srcCompat="#drawable/tank_progress" />
I then try and dynamically change the height (only height, not width) of the image when the method is called in its class, as follows...
public void updateTank() {
ImageView myTank = (ImageView)findViewById(R.id.tankHeight);
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)
myTank.getLayoutParams();
params.height = 350 - 35;
myTank.setLayoutParams(params);
}
However, when this method is called, the image appears to scale by the specified amount in both height and width, when I only want to reduce the height by 35 pixels. My understanding is that this may be due to using a relative layout, but I am not sure how to overcome the issue, so that I can programmatically change the height only. From reading this and this, I had thought the method was set up correctly, so am unsure why it is displaying differently than intended?
The default ScaleType for an ImageView is FIT_CENTER. What this means is that the source image will be scaled so that you can see the entire thing, regardless of the view's aspect ratio... so even though your ImageView is probably only changing its height, the image content is being scaled in both dimensions despite only having the view height change.
Try setting android:scaleType="centerCrop" on your ImageView tag. This will allow you to resize the view without re-scaling the image contents.
More info on scale types: https://robots.thoughtbot.com/android-imageview-scaletype-a-visual-guide
Changing a view height and/or width requires it to be redrawn, try calling requestLayout(). Hope it helps.
public void updateTank() {
ImageView myTank = (ImageView)findViewById(R.id.tankHeight);
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)
myTank.getLayoutParams();
params.height = 350 - 35;
myTank.setLayoutParams(params);
myTank.requestLayout();
}
I believe you should first convert dp to pixel.
public int convertDpToPixel(float dp) {
Context context = getContext();
if (context == null) {
return 0; // context should never be a null
}
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
return (int) (dp * ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT));
}
and then you can call
myTank.getLayoutParams();
params.height = convertDpToPixel(350) - convertDpToPixel(35);
myTank.setLayoutParams(params);
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 am currently creating an Android app where someone can input their name, press a button, and then it just outputs their name back to them.
One effect that I would like to achieve with this is an effect where, after they push the button, the input and button will vanish (complete this bit so far), and then the background colour of the MainActivity's view will do a ripple (from the centre) with a new colour, eventually changing the full background colour.
How would I go about doing this programatically, since I am only able to find tutorials on adding ripples to buttons when pushed?
EDIT:
I tested this by making a small app
First of all hide the view you want to reveal in this animation.
The view can be from the same layout and in xml its visibility should be invisible so that the animation will reveal it.
You can set the view height and width to match parent if you want to create a full screen animation...
Take your original and reveal view both in frame layout
In my case,I have used this:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:text="Hello World!"
android:layout_width="wrap_content"
android:textSize="20sp"
android:layout_height="wrap_content" />
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorPrimaryDark"
android:id="#+id/revealiew"
android:visibility="invisible"
>
</FrameLayout>
then in your activity on button click or some event do this:
fab.setOnClickListener(new View.OnClickListener() {
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public void onClick(View view) {
// previously invisible view
View myView = findViewById(R.id.revealview);
// get the center for the clipping circle
int cx = myView.getWidth() / 2;
int cy = myView.getHeight() / 2;
// get the final radius for the clipping circle
int finalRadius = Math.max(myView.getWidth(), myView.getHeight());
// create the animator for this view (the start radius is zero)
Animator anim =
ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
//Interpolator for giving effect to animation
anim.setInterpolator(new AccelerateDecelerateInterpolator());
// Duration of the animation
anim.setDuration(1000);
// make the view visible and start the animation
myView.setVisibility(View.VISIBLE);
anim.start();
}
});
}
You can take detailed look at official documentation here:
http://developer.android.com/training/material/animations.html
What you are describing is a reveal effect on the background.
From the official doc you can find ready to use examples:
1) Here is how to reveal a previously invisible view using reveal effect:
// previously invisible view
View myView = findViewById(R.id.my_view);
// get the center for the clipping circle
int cx = myView.getWidth() / 2;
int cy = myView.getHeight() / 2;
// get the final radius for the clipping circle
int finalRadius = Math.max(myView.getWidth(), myView.getHeight());
// create the animator for this view (the start radius is zero)
Animator anim =
ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
// make the view visible and start the animation
myView.setVisibility(View.VISIBLE);
anim.start();
2) Here is how to hide a previously visible view using the reveal effect:
// previously visible view
final View myView = findViewById(R.id.my_view);
// get the center for the clipping circle
int cx = myView.getWidth() / 2;
int cy = myView.getHeight() / 2;
// get the initial radius for the clipping circle
int initialRadius = myView.getWidth();
// create the animation (the final radius is zero)
Animator anim =
ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0);
// make the view invisible when the animation is done
anim.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
myView.setVisibility(View.INVISIBLE);
}
});
// start the animation
anim.start();
In you app, you can use a colored background layer (invisible at the beginning) and then use the reveal effect on it.
check this site, "Android Ripple Background" is a library to do it and the min sdk is 11 (Android 3.0 Honeycomb) https://android-arsenal.com/details/1/1107
Essentially, I am re-asking this question but for implementing it on android.
I am trying to allow users to swipe between filters on a static image.
The idea is that the image stays in place while the filter scrolls
above it. Snapchat recently released a version which implements this
feature. This video shows exactly what I'm trying to accomplish at
1:05.
I tried filling a list with the overlays and paging through it with the onFling and drawing with onDraw, but I lose the animations. Is there a way this can be done with ViewPager?
EDIT: As requested, I have provided my implementation for overlay view paging. It fills the viewpager with transparent png images which sits on top of an image view. Also, this code is in C#, as I am using Xamarin Android. It's fairly similar to Java for those unfamiliar with C#
...
static List<ImageView> overlayList = new List<ImageView>();
...
public class OverlayFragmentAdapter : FragmentPagerAdapter
{
public OverlayFragmentAdapter(Android.Support.V4.App.FragmentManager fm) : base(fm)
{
}
public override int Count
{
get { return 5; } //hardcoded temporarily
}
public override Android.Support.V4.App.Fragment GetItem(int position)
{
return new OverlayFragment ();
}
}
public class OverlayFragment : Android.Support.V4.App.Fragment
{
public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.Inflate (Resource.Layout.fragment_overlay, container, false);
LinearLayout l1 = view.FindViewById<LinearLayout> (Resource.Id.overlay_container);
ImageView im = new ImageView (Activity);
im.SetImageResource (Resource.Drawable.Overlay); //Resource.Drawable.Overlay is a simple png transparency I created. R
l1.AddView (im);
overlayList.AddElement (im);
return view;
}
}
Activity Layout XML:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="bottom">
<ImageView
android:id="#+id/background_image"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<RelativeLayout <!-- This second layout is for buttons which I have omitted from this code -->
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="#+id/edit_layout">
<android.support.v4.view.ViewPager
android:id="#+id/overlay_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
</RelativeLayout>
Fragment Overlay XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/overlay_container"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center" />
To briefly summarize: the viewpager sits on top of the first imageview, which acts as a background. The OnCreateView method creates an overlay fragment and an overlay imageview from a resource, which it puts inside the overlay_container layout. Saving the image (Which I have not posted as it is outside the scope of this question) is simple, all it does is create a background bitmap, an overlay bitmap, and uses a canvas to draw the overlay onto the background, then writes to file.
I've worked on something similar myself.
For your specific use case, I would just use a canvas and alpha blend the filters across, on fling, as the top image.
To do the alpha blending, set the alpha paint of the first image (the original) to 255 and the alpha of the second one (the filter) to something like 128.
You just need a filter with the size of the image and then you shift the position of the second image as you draw it. That's it.
It's extremely fast and works a treat on very, very old devices.
Here's a sample implementation:
Bitmap filter, // the filter
original, // our original
tempBitmap; // the bitmap which holds the canvas results
// and is then drawn to the imageView
Canvas mCanvas; // our canvas
int x = 0; // The x coordinate of the filter. This variable will be manipulated
// in either onFling or onScroll.
void draw() {
// clear canvas
mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
// setup paint
paint0.setAlpha(255); // the original needs to be fully visible
paint1.setAlpha(128); // the filter should be alpha blended into the original.
// enable AA for paint
// filter image
paint1.setAntiAlias(true);
paint1.setFlags(Paint.ANTI_ALIAS_FLAG); // Apply AA to the image. Optional.
paint1.setFlags(Paint.FILTER_BITMAP_FLAG); // In case you scale your image, apple
// bilinear filtering. Optional.
// original image
paint0.setAntiAlias(true);
paint0.setFlags(Paint.ANTI_ALIAS_FLAG);
paint0.setFlags(Paint.FILTER_BITMAP_FLAG);
// draw onto the canvas
mCanvas.save();
mCanvas.drawBitmap(original, 0,0,paint0);
mCanvas.drawBitmap(filter, x,0,paint1);
mCanvas.restore();
// set the new image
imageView.setImageDrawable(new BitmapDrawable(getResources(), tempBitmap));
}
And here are basic onFling and onScroll implementations.
private static final int SWIPE_DISTANCE_THRESHOLD = 125;
private static final int SWIPE_VELOCITY_THRESHOLD = 75;
// make sure to have implemented GestureDetector.OnGestureListener for these to work.
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
float distanceX = e2.getX() - e1.getX();
float distanceY = e2.getY() - e1.getY();
if (Math.abs(distanceX) > Math.abs(distanceY) && Math.abs(distanceX) >
SWIPE_DISTANCE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
// change picture to
if (distanceX > 0) {
// start left increment
}
else { // the left
// start right increment
}
}
}
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// checks if we're touching for more than 2f. I like to have this implemented, to prevent
// jerky image motion, when not really moving my finger, but still touching. Optional.
if (Math.abs(distanceY) > 2 || Math.abs(distanceX) > 2) {
if(Math.abs(distanceX) > Math.abs(distanceY)) {
// move the filter left or right
}
}
}
Note: The onScroll/onFling implementations have pseudo code for the x adjustments, as those functions need to be tested. Someone who ends up implementing this in the future, can feel free to edit the answer and provide those functions.
Take a look in the implementation of the method onDraw for the default Calendar app: DayView.
There is onFling implementation and redrawing of the content (for example, calendar grid) according to the motion changes, which imitates fling.
Then you can use ColorFilter in onDraw according to the motion changes. It is very fast.
Alternatively, you can use ViewSwitcher with a list of filtered images (or somehow created a filtered images cache). To achieve the possibility of "drawing over the image", you can use ImageView and ViewSwitcher in RelativeLayout one above another and set the new filtered image in ImageView after the end of scrolling.
For this application i feel it would be most easy to use androids animation features and set the animations value to the filter you want. So you would make your own animation the changed filters iterating over your array.
I am writing a small game and I want the game canvas to keep its proportions and always be in landscape orientation.
So I have a code like this:
activity_game.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:gravity="center"
android:background="#Ff0000" <!-- red -->
tools:context=".EngineActivity">
<com.example.arsen.pw3.game.GameLayout
android:id="#+id/mainWrapper"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#00Ff00" <!-- green -->
android:gravity="center">
<!-- canvas and all additional stuff comes here -->
</com.example.arsen.pw3.game.GameLayout>
</RelativeLayout>
GameLayout.java:
public class GameLayout extends RelativeLayout {
public GameLayout(Context context) {
super(context);
}
public GameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public GameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
// overriding onSizeChanged to take care that the proportions will be kept.
#Override
public void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
super.onSizeChanged(width, height, oldWidth, oldHeight);
double ratio;
double canvasRatio = width / (double) height;
GameSettings.DisplaySize displaySize = GameSettings.getInstance().displaySize;
double gameDisplayRatio = displaySize.height / displaySize.width;
if(canvasRatio > gameDisplayRatio) {
ratio = width / displaySize.width;
} else {
ratio = height / displaySize.height;
}
getLayoutParams().height = (int) (ratio * displaySize.height);
getLayoutParams().width = (int) (ratio * displaySize.width);
}
}
It works properly until I return to application after switching to other.
This is how it looks when I run the app, but once I open the system switcher and then return to it, it looks like this.
So it looks that the width and height I get in onSizeChanged() method is the one from before the landscape orientation is set, and for some reason the method is not called again with the right width once the orientation is changed.
What am I doing wrong here?
According to the following link: Views inside a custom ViewGroup not rendering after a size change
You will need to had the following code at the end of your onSizeChanged to force a call to requestLayout()
Handler handler = new Handler();
handler.post(new Runnable() {
#Override
public void run() {
requestLayout();
}
});
As I mentioned in a comment to moise's answer, I found a workaround here. I added this code at the begining of the OnSizeChanged (right after calling super.onSizeChanged):
if(width < oldWidth) {
width = oldWidth;
height = oldHeight;
}
The point here was so that the layout will never get smaller, so when it initializes with right size it will stay this way forever. It wasn't a problem for me, that this would cause it to look bad in portrait orientation, because the app was supposed to work only in landscape mode. But now when I force portrait orientation, despite the expectations, it scales correctly (and then scales correctly after switching back to landscape).
So honestly, I have no idea what is happening, but this piece of code solved my problem...
I'll come back to this problem later and try to find a better solution, or at least a logical explanation for this one, but not now...
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;
}