I am trying to make square child inside RelativeLayout(which I extend). On activity start it works ok, but when ad is loaded, my relative layout gets smaller but my code does not affect child.
Code from extended RelativeLayout:
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
int minSize = Math.min(w, h);
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) imageViewContainer.getLayoutParams();
layoutParams.width = minSize;
layoutParams.height = minSize;
imageViewContainer.setLayoutParams(layoutParams);
invalidate();
}
Try to change the values overriding onMeasure method, also in Your code I would either called measure() or requestLayout() by myself
Related
I have a custom LinearLayout class which I'm using to clip all of its child Views through Canvas#clipPath():
public class MaskedLinearLayout extends LinearLayout {
protected Path mMaskingClip = new Path();
(...)
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// Update clipping path
mMaskingClip.reset();
(...)
mMaskingClip.close();
}
#Override
public void dispatchDraw(Canvas canvas) {
int save = canvas.save();
canvas.clipPath(mMaskingClip);
super.dispatchDraw(canvas);
canvas.restoreToCount(save);
}
}
The code above works great, but the background Drawable that I've set for the same MaskedLinearLayout is not clipped to the path that I've defined (only its children).
How should I accomplish this? Some things that I've tried include overriding method onDraw() (with similar code shown in method dispatchDraw() above) and setting its background Drawable boundaries manually (Drawable#setBounds()), both without success.
Note that I cannot use static Drawables in this layout, because the latter should be able to be resized/masked dynamically while being displayed on the screen.
Thank you in advance for any answer.
I have a horizontal scrollview (which has a single view group inside, obviously) which I'm adding some views into when my activity is created. I want a view to start and end at the middle so I need to give some paddings to right and left respectively to the first and last view. Since the sizes are not measured at onCreate, I calculated them on onSizeChanged but the sizes of views are not calculated yet so no prevail there.
public void add(ArrayList<String> list) {
for(String string : list) {
View view = inflater.inflate(R.layout.textview_with_indicator, null);
views.add(view);
((TextView)view.findViewById(R.id.text)).setText(string);
content.addView(view);
}
invalidate();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if(paddingSet && null != views && views.size() > 0) {
int paddingLeft = getWidth() - (views.get(0).getWidth()/2);
int paddingRight = getWidth() - (views.get(views.size()-1).getWidth()/2);
setPadding(paddingLeft, getPaddingTop(), paddingRight, getPaddingBottom());
paddingSet = true;
}
}
I think I need to observe the size change of views instead of whole content but I have no idea how to implement it. I tried to implement onGlobalFocusChanged to scrollview but could not make it work. Any ideas would be appreciated.
#Dan S, gave the below statement in this post -> display a still image map
Then using a second ImageView (for the indicator) draw it on top and
move it to the correct place on the screen and make sure the
background in this View is transparent as well as the background of
your indicator
How can I create the second ImageView for my indicator? It should be transparent imageview as well as the indicator image.
UPDATE:
I am asking here on how to overlap my image map ImageView and transparent indicator ImageView in order to mark the current location.
An ImageView is transparent by default.
Your indicator image resource should have a transparent background (e.g. transparent .PNG image).
Just set the ImageView's imageResource or backgroundResource to the PNG file.
So your code for creating your ImageView will be something like this:
ImageView myImageView = new ImageView(context);
myImageView.setBackgroundColor(0x0); // not needed - it's the default
myImageView.setImageResource(R.drawable.indicator_icon);
but again, this's the default and you still have to make sure your image's background is transparent otherwise the ImageView's background won't matter.
UPDATE: following #eros update of the question, here's an updated answer:
There are two options, i can think of, to achieve positioning of one imageview on top of the other:
use the LayoutParams and set the margins to the position the indicator imageview
draw the indicator imageview on top of the map bmp's canvas
personally i like the first option better because future changes won't force you to repaint the indicator.
here's a class demonstrating option (1):
public class MyMapView extends RelativeLayout {
private ImageView mBackImageView;
private ImageView mIndicatorImageView;
public MyMapView(Context context) {
super(context);
mBackImageView = new ImageView(context);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
mBackImageView.setImageResource(R.drawable.image1);
mBackImageView.setScaleType(ImageView.ScaleType.FIT_XY);
addView(mBackImageView, params);
mIndicatorImageView = new ImageView(context);
RelativeLayout.LayoutParams indParams = new RelativeLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
mIndicatorImageView.setImageResource(R.drawable.image2);
addView(mIndicatorImageView, indParams);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
centerIndicatorPosition();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerIndicatorPosition();
}
// #eros: this's the part you want. this method just centers the indicator view
// but if you have the relative position like you say, use it for the margins
private void centerIndicatorPosition() {
int xPos = (getMeasuredWidth() - mIndicatorImageView.getMeasuredWidth()) / 2;
int yPos = (getMeasuredHeight() - mIndicatorImageView.getMeasuredHeight()) / 2;
((RelativeLayout.LayoutParams)mIndicatorImageView.getLayoutParams())
.setMargins(xPos, yPos, 0, 0);
}
}
Is it possible to add views to a layout during the onLayout event of one of its Childs?
i.e.
FrameLayout contains View, in View.onLayout() I want to add views to the parent FrameLayout.
This is because the views I need to draw on the FrameLayout needs the child View dimensions (width, height) to assign them to particular positions on the FrameLayout.
I already try to do so, but nothing is getting drawn. Do you know how can I accomplish the same effect? or if I'm doing something wrong. Don't know why I'm unable to draw the views, event if I call invalidate.
Thanks.
Yes, it's possible. I have solved similar problem (placing a checkpoint Button into FrameLayout over SeekBar) using the following code (overriden methods from SeekBar):
#Override
protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) {
super.onLayout(changed, left, top, right, bottom);
View child = new Button(getContext());
//child measuring
int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, 0, LayoutParams.WRAP_CONTENT); //mWidthMeasureSpec is defined in onMeasure() method below
int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);//we let child view to be as tall as it wants to be
child.measure(childWidthSpec, childHeightSpec);
//find were to place checkpoint Button in FrameLayout over SeekBar
int childLeft = (getWidth() * checkpointProgress) / getMax() - child.getMeasuredWidth();
LayoutParams param = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
param.gravity = Gravity.TOP;
param.setMargins(childLeft, 0, 0, 0);
//specifying 'param' doesn't work and is unnecessary for 1.6-2.1, but it does the work for 2.3
parent.addView(child, firstCheckpointViewIndex + i, param);
//this call does the work for 1.6-2.1, but does not and even is redundant for 2.3
child.layout(childLeft, 0, childLeft + child.getMeasuredWidth(), child.getMeasuredHeight());
}
#Override
protected synchronized void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//we save widthMeasureSpec in private field to use it for our child measurment in onLayout()
mWidthMeasureSpec = widthMeasureSpec;
}
There is also ViewGroup.addViewInLayout() method (it's protected, so you can use it only if you override onLayout method of your Layout) which javadoc says its purpose is exactly what we discuss here, but I haven't understood why is it better than addView(). You can find it's usage in ListView.
I am working on a subclass of FrameLayout that is supposed to rotate all of its children by 90 degrees. I am doing this to overcome the landscape-only camera limitation present in android 2.1 and below, by having the activity be in landscape, but placing my camera overlay into this framelayout overlay to cause it to appear as if it was portrait (this is how Layar does it) To accomplish this, I'm adapting Jeff Sharkey's code to rotate views. My problem is that I can rotate the Framelayout, but I cannot resize it to match the new dimensions. So on my g1, instead of a 320x480 portrait view over a 480x320 camera view in landscape, I get a 320x320 box in the middle showing my portrait view with the sides chopped off.
Here is my code so far:
public class RotateLayout extends FrameLayout {
private Matrix mForward = new Matrix();
private Matrix mReverse = new Matrix();
private float[] mTemp = new float[2];
public RotateLayout(Context context) {
super(context);
}
public RotateLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
/* (non-Javadoc)
* #see android.widget.FrameLayout#onMeasure(int, int)
*/
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//This didn't work:
//super.onMeasure(heightMeasureSpec, widthMeasureSpec);
}
/* (non-Javadoc)
* #see android.widget.FrameLayout#onSizeChanged(int, int, int, int)
*/
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
#Override
protected void dispatchDraw(Canvas canvas) {
canvas.rotate(270, getWidth()/2, getHeight()/2);
//This code will stretch the canvas to accommodate the new screen size. This is not what I want.
//float scaleX=(float)getHeight()/getWidth();
//float scaleY=(float)getWidth()/getHeight();
//canvas.scale(scaleX, scaleY, getWidth()/2, getHeight()/2);
mForward = canvas.getMatrix();
mForward.invert(mReverse);
canvas.save();
canvas.setMatrix(mForward); //This is the matrix we need to use for proper positioning of touch events
super.dispatchDraw(canvas);
canvas.restore();
}
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
final float[] temp = mTemp;
temp[0] = event.getX();
temp[1] = event.getY();
mReverse.mapPoints(temp);
event.setLocation(temp[0], temp[1]);
return super.dispatchTouchEvent(event);
}
}
I have tried overriding OnMeasure to switch the X and Y dimensions of the View, but have not been able to get that to work.
Any help you can provide is greatly appreciated.
I had the same problem and managed to solve it.
Instead of rotating each view or the layout by hand, I used a LayoutAnimationController.
First, place a file in /res/anim/ called rotation.xml
<?xml version="1.0" encoding="utf-8"?>
<rotate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="-90"
android:pivotX="50%"
android:pivotY="50%"
android:duration="0" android:fillAfter="true">
</rotate>
Then, in your Activity's onCreate, do
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.myscreen);
Animation rotateAnim = AnimationUtils.loadAnimation(this, R.anim.rotation);
LayoutAnimationController animController = new LayoutAnimationController(rotateAnim, 0);
FrameLayout layout = (FrameLayout)findViewById(R.id.MyScreen_ContentLayout);
layout.setLayoutAnimation(animController);
}
If you want to rotate elements that lie above your camera preview view (SurfaceHolder), simply place a FrameLayout above the SurfaceHolder, place all your elements in that FrameLayout and call the Layout "MyScreen_ContentLayout". Done.
Hope that helped someone out, took me quite a while to get everything together.
Using API level 11 and later you can use the method setRotation(degreesFloat); to change the rotation of a view programmatically, or you can use the XML attribute android:rotation="" to change it in your XML. There are also methods/attributes for changing only the X or Y values of a view's rotation: Android Docs - View (setRotation).
So nowadays as long as you're using API level 11 or above, you should be able to apply the rotation to a wrapper layout node. However, you probably will also have to change the dimensions of the top-level layout to match the dimensions you desire after the rotation. I.e. if you have a portrait view w/ dimensions 800x1280, you'll have to change them to 1280x800 in order for it to line up after rotating to landscape.
Using this library you can rotate whole view hierarchy https://github.com/rongi/rotate-layout
Like this
This is what has worked for me in general.
private void init() {
setRotation(90f);
}
public YourViewOrViewGroup(final Context context) {
super(context);
init();
}
... (all the View/ViewGroup constructors) ...
#Override
protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) {
super.onLayout(changed, l, t, r, b);
final int width = getWidth();
final int height = getHeight();
final int offset = Math.abs(width - height) / 2;
setTranslationX(-offset);
setTranslationY(offset);
}
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
}
What you want to do is swap the width with height and then put the X & Y offsets so that the view becomes full screen after the rotation.
The above is a 'landscape'-rotated version. To achieve a landscape inverted just apply 270-deg rotation. You can either modify code within the snippet or apply the rotation outside in a more generic way, i.e
final YourViewOrViewGroup layout = inflater.inflate(...);
if (currentOrientation.isInverted()) {
layout.setRotation(layout.getRotation + 180f);
}
this way you are able to embed the rotated View/ViewGroup within the xml definition and inflate 'port' and 'land' versions while the screen orientation changes, but this seems out of this topic.
Edit: actually it is much better to defer the offset setting until at least one layout pass is over.
This is due the fact that in my case after first onMeasure() the view would be drawn (before the offsets were set). Eventually it could be experienced as glitching because the view/layout would not get drawn within the final bounds at first.
(updated the snippet)
I think you forgot one line in your onMeasure.
#Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
Taken from the code for a vertical seekbar here:
How can I get a working vertical SeekBar in Android?
You might need to fiddle with the getMeasuredHeight() to make it the correct size of your screen.
Try turning off children clipping of your view root: call setClipChildren(false) on parent of your RotateLayout and in onMeasure method of your RotateLayout put these lines:
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
I'm having basically the same problem as you and I still haven't tested my solution - I'll do it tomorrow and tell if it is working correctly.