Rotate View Hierarchy 90 degrees - android

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.

Related

View with the same size of another

I need to create a View Placeholder: a View that doesn't draw nothing, but has the same dimension of a given View. I'm trying to achieve that by doing so:
public class ViewPlaceHolder extends View {
View target;
public ViewPlaceHolder(View target) {
super(target.getContext());
this.target = target;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
target.measure(widthMeasureSpec, heightMeasureSpec);
super.onMeasure(
MeasureSpec.makeMeasureSpec(target.getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(target.getMeasuredHeight(), MeasureSpec.EXACTLY));
}
}
By now it doesn't work as expected. Any suggestions?
Thanks a lot
PS: I need a placeholder to make same animations and effects on a scrollView
PPS: additional explanation:
the result of target.getMeasuredWidth() and target.getMeasuredHeight() is correct, but the ViewPlaceholder has a complete random height (the width seem to be ok)
Sorry, I made an error on the layout hierarchy, now it works fine!
If the target view is drawn , then you can read its dimensions by calling the getWidth() and getHeight() methods. So, if these methods does not return 0 , you can apply the setLayoutParams on the ViewPlaceHolder. The only thing that you have to keep in mind is the Layout that will lay your ViewPlaceHolder. Suppose it is a RelativeLayout you can do something like this inside the ViewPlaceHolder constructor body :
public ViewPlaceHolder(View target) {
super(target.getContext());
this.target = target;
int height = target.getHeight();
int width = target.getWidth();
this.setLayoutParams(new RelativeLayout.LayoutParams(width,height));
this.invalidate();
}

create a 16:9 view

I am writing a program that I would like to have a 16:9 screen all the time. I set up a basic linearlayout to host 2 object that I write. The first one overwrite the onMeasure method, so that it'll take a "square" space from the screen, and the second object take the rest. This looks good on a 16:9 device that I have. But when I tried it out on other device, it just looks bad. I tried to extend from the linearlayout that host my object, and overwrite the onMeasure method for the layout. The custom Linearlayout seems to do the 16:9 fine, but my first object (the square), is still getting the big square, not a smaller square confined to the 16:9 strip. Here are the relavent code
MainActivity:
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
layout = new MyLinear(this);
mField = new Field(this);
mField.setId(1);
control = new Controller(this);
control.setId(2);
layout.addView(mField);
layout.addView(control);
registerForContextMenu(control);
setContentView(layout);
mField.requestFocus();
}
the onMeasure code for the custom linearlayout looks like this
public class MyLinear extends LinearLayout{
private int height, width; // dimension of the screen
private Context m_context;
public MyLinear(Context context) {
super(context);
m_context=context;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height_temp = View.MeasureSpec.getSize(heightMeasureSpec);
int width_temp = View.MeasureSpec.getSize(widthMeasureSpec);
double ratio = width_temp/(double)height_temp;
int final_height = (int) (width_temp/1.77);
height=final_height;
if (ratio<1.7) {
setMeasuredDimension(width_temp, (int) (width_temp/1.77));
} else {
setMeasuredDimension(width_temp, height_temp);
}
}
anyone has a better suggestion?
You edit, view.xml file. Add res/layout-xlarge/my_layout.xml folder. after add my_layout.xml file res/layout-xlarge/ folder... I'm sorry for bad english

Fixed height in horizontallistview does not show the correct height

I'm using the HorizontalListView provided here, and I'm trying to show custom views with a fixed height and width, like this:
public class MyView extends View {
public MyView(Context context) {
super(context);
init();
}
private void init() {
setBackgroundColor((int) (Math.random() * Integer.MAX_VALUE));
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
int h = getHeight(); // shows 255, correct height
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int w = MeasureSpec.makeMeasureSpec(425, MeasureSpec.EXACTLY);
int h = MeasureSpec.makeMeasureSpec(255, MeasureSpec.EXACTLY);
super.onMeasure(w, h);
}
}
When showing this MyView in a normal LinearLayout, the height and width are perfect. However, when I show the view in the HorizontalListView, the width is perfect, but the height is not. See this screenshot:
The width is 425px, which is correct, the height is only 160px instead of 255.
In the source of the HorizontalListView there is this method:
private void addAndMeasureChild(final View child, int viewPos) {
LayoutParams params = child.getLayoutParams();
if(params == null) {
params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
}
addViewInLayout(child, viewPos, params, true);
child.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
}
Is there something I should change in this method, or is there something else to make this work?
When you do int h = MeasureSpec.makeMeasureSpec(255, MeasureSpec.EXACTLY);, that 255 value is a PX value. If you said the height was 200 DP in the XML that value will be adjusted according to the device. Not sure if the screen shown is HDPI or XHDPI (my guess is the latter), it will be multiplied, so your 200 DP becomes 300 PX in an HDPI phone (x1.5). You can either use a PX value in the XML or you can define a dimension DP value that's 200DP in a values.xml.
The preference would be the latter. You can use that dimension value in your XML and retrieve that from the Resources when you create your View. That means that if at some point you want to make it bigger you can just change one value and you're done.
Edit
I see you found the real problem. In any case, you should still use the dimension approach, it will make your code cleaner and it will work in any phone (255 will only look right in a specific combination).

Android Custom Layout - onDraw() never gets called

public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TableView tv = new TableView(this);
tv.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT));
setContentView(tv);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
public class TableView extends ViewGroup {
private Paint oval;
private RectF rect;
public TableView(Context context) {
super(context);
oval= new Paint(Paint.ANTI_ALIAS_FLAG);
oval.setColor(Color.GREEN);
}
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawOval(rect , oval);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int wspec = MeasureSpec.makeMeasureSpec(
getMeasuredWidth(), MeasureSpec.EXACTLY);
int hspec = MeasureSpec.makeMeasureSpec(
getMeasuredHeight(), MeasureSpec.EXACTLY);
for(int i=0; i<getChildCount(); i++){
View v = getChildAt(i);
v.measure(wspec, hspec);
}
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
float w=r-l;
float h=b-t;
rect=new RectF(w/8,h/8,7*w/8,7*h/8);
float theta = (float) (2 * Math.PI / getChildCount());
for(int i=0; i< getChildCount(); i++) {
View v = getChildAt(i);
w = (rect.right-rect.left)/2;
h = (rect.bottom-rect.top)/2;
float half = Math.max(w, h)/2;
float centerX = rect.centerX()+w*FloatMath.cos(theta);
float centerY = rect.centerY()+h*FloatMath.sin(theta);
v.layout((int)(centerX-half),(int)(centerY-half),(int)(centerX+half),(int)(centerY+half));
}
}
}
Well there are almost NONE good and deep tutorials and hardly any piece of data on how to do custom layouts right, so i tried to figure out how its done, what i am trying to implement is a Layout that paints a green oval at the center of the screen, and i want every child of this layout to be layed out around the oval.
you can think of that oval as a poker table that i want the children of this layout to seat around it.
What currently happens by this code is that i get a white app scren with no oval, so i debugged it and saw that onDraw never gets called...
3 questions:
why is onDraw not getting called?
the sdk warns me that i shouldnt allocate new objects within onLayout method, so where should i calculate the RectF so it is ready for the onDraw call to come?
does calling super.onDraw() would make all children paint themselves? or should i explicitly invoke their draw()?
If I got it all wrong and you guys can guide me in the right direction, or have any links to examples or tutorials related to this subject that would be helpful too!
By default, onDraw() isn't called for ViewGroup objects. Instead, you can override dispatchDraw().
Alternatively, you can enable ViewGroup drawing by calling setWillNotDraw(false) in your TableView constructor.
EDIT:
For #2:
- Initialize it in the constructor, then just call rect.set() in your onLayout() method.
For #3:
- Yes, as far as I'm aware the super call will handle it, you shouldn't have to handle that unless you need to customize the way the children are drawn.
If you want that the canvas is be re-draw call invalidate(), and the method onDraw() will be re-executed

OnClick listener does not work after rotate ImageView?

I am using the below code to rotate an ImageView Control
ImageView Iv = new this.findViewById(id)
RotateAnimation a = new RotateAnimation(0, 60);
a.setFillAfter(true);
a.setFillBefore(false);
a.setDuration(0);
Iv.startAnimation(a);
The problem with the above code is that the ImageView looks rotated, but can't be clicked correctly. The coordinates that trigger the click event are the ones at the area the control had before beeing rotated.
Also I tried another solution,, I tried to write a custom view extends ImageView and rotate the control canvas in onDraw() but this solution did not rotate the control , it rotates the content of the control.
#Override
protected void onDraw(Canvas canvas)
{
canvas.rotate(60, getWidth() / 2, getHeight() / 2);
super.onDraw(canvas);
canvas.save();
canvas.restore();
}
#Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
any idea how can I rotate the control itself and keep it clickable ?
Thanks in advance,
Animation doesn't change your views position. It's a common issue with animation. Please look here.

Categories

Resources