Android Layout views rotated and spaced around a circle? - android

I'm trying to figure out a way to layout a series of views around a circle, in such a way that each view is rotated to be facing outward from the circle. The picture below is a rough sketch of what I'm looking for. The outside block is the layout/viewgroup, the red squares represent the views I want to rotate.
I'm familiar with the PivotX, PivotY, and Rotation view properties and suspect I will be making use of these in some way, but I'm not sure how to use these in concert with an appropriate layout to get the desired effect.

Here's an example that does this. I created a new Android project and replaced the RelativeLayout that's already there, with a FrameLayout. It's >= API 11 only because of translate and rotate calls in View:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="#string/hello_world" />
</FrameLayout>
I'll create some quick views in code, just replace them with whatever views you have. I'm placing them all in the center of the layout, by setting the gravity of their LayoutParams to Gravity.CENTER. Then, I'm translating and rotating them to their correct positions:
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final FrameLayout main = (FrameLayout)findViewById(R.id.main);
int numViews = 8;
for(int i = 0; i < numViews; i++)
{
// Create some quick TextViews that can be placed.
TextView v = new TextView(this);
// Set a text and center it in each view.
v.setText("View " + i);
v.setGravity(Gravity.CENTER);
v.setBackgroundColor(0xffff0000);
// Force the views to a nice size (150x100 px) that fits my display.
// This should of course be done in a display size independent way.
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(150, 100);
// Place all views in the center of the layout. We'll transform them
// away from there in the code below.
lp.gravity = Gravity.CENTER;
// Set layout params on view.
v.setLayoutParams(lp);
// Calculate the angle of the current view. Adjust by 90 degrees to
// get View 0 at the top. We need the angle in degrees and radians.
float angleDeg = i * 360.0f / numViews - 90.0f;
float angleRad = (float)(angleDeg * Math.PI / 180.0f);
// Calculate the position of the view, offset from center (300 px from
// center). Again, this should be done in a display size independent way.
v.setTranslationX(300 * (float)Math.cos(angleRad));
v.setTranslationY(300 * (float)Math.sin(angleRad));
// Set the rotation of the view.
v.setRotation(angleDeg + 90.0f);
main.addView(v);
}
}
And this is the result:

The answer by #Daniel is great.
If you need more functionality you can use this nice library on GitHub called Android-CircleMenu

Related

Scale ImageView and clip bottom only when outside parent view?

how does one scale an ImageView, yet still have it clip (bottom only) when it/parts of it are outside its parent view?
My code;
XML has 3 elements; image in raw size, view 200px high, view 150px high.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="1000px"
android:layout_height="850px"
android:background="#FFFEC6"
android:orientation="vertical">
<!-- single image in raw size -->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/fauxcard"
/>
<!-- should be SCALED with NO CLIPPING -->
<RelativeLayout
android:id="#+id/Wrapper1"
android:layout_width="1000px"
android:layout_height="200px"
android:background="#cccccc">
</RelativeLayout>
<!-- should be SCALED with the BOTTOM CLIPPED -->
<RelativeLayout
android:id="#+id/Wrapper2"
android:layout_width="1000px"
android:layout_height="150px"
android:background="#C6FFD1"
android:clipChildren="true">
</RelativeLayout>
</LinearLayout>
java;
RelativeLayout r1 = (RelativeLayout) findViewById(R.id.Wrapper1);
RelativeLayout r2 = (RelativeLayout) findViewById(R.id.Wrapper2);
int marginLeft = 0;
for (int i = 0; i < 10; i++)
{
// IV 1 - the parent view for these is the correct 200 high, so these will be unclipped/unscaled
ImageView myImg1 = new ImageView(this);
myImg1.setImageResource(R.drawable.fauxcard);
RelativeLayout.LayoutParams layout1 = new RelativeLayout.LayoutParams(100, 200);
layout1.setMargins(marginLeft, 0, 0, 0);
r1.addView(myImg1, layout1);
// IV 2 - the parent view for these is NOT high enough (only 150 high), so I want these SCALED but CLIPPED (bottom of image clipped)
ImageView myImg2 = new ImageView(this);
myImg2.setImageResource(R.drawable.fauxcard);
// scaling
// myImg2.setScaleType(ImageView.ScaleType.CENTER); // NOT scaled - clipped
// myImg2.setScaleType(ImageView.ScaleType.CENTER_INSIDE); // scaled - NOT clipped
myImg2.setScaleType(ImageView.ScaleType.CENTER_CROP); // scaled - clipped at TOP and bottom
// myImg2.setScaleType(ImageView.ScaleType.FIT_END); // scaled TOO MUCH - clip not needed
// myImg2.setScaleType(ImageView.ScaleType.FIT_START); // scaled TOO MUCH - clip not needed
// myImg2.setScaleType(ImageView.ScaleType.FIT_CENTER); // scaled TOO MUCH - clip not needed
// myImg2.setScaleType(ImageView.ScaleType.FIT_XY); // scaled incorrect ratio - clip not needed
// myImg2.setScaleType(ImageView.ScaleType.MATRIX); // NOT scaled - clipped
RelativeLayout.LayoutParams layout2 = new RelativeLayout.LayoutParams(100, 200);
layout2.setMargins(marginLeft, 0, 0, 0);
r2.addView(myImg2, layout2);
// I am using marginLeft as my real scenario is more complex - there are varying gaps between the images based on other logic
// the important issue is CLIPPING the images whilst scaling them
marginLeft += 100;
}
To both of the RelativeLayouts I'm adding 10 images. I want both sets of images to be scaled the same, but as the 2nd RL isn't high enough I want those images CLIPPED (the bottom of the image gone).
I've tried all ScaleTypes, but none of them achieve this. The closest is CENTER_CROP, which has the correct scaling, but both the top and bottom are clipped (and, again, I just want the bottom clipped).
Here's a image;
https://imgur.com/a/QLJkm
put this code in viewtreeobserver or you can have custom imageview
Matrix matrix = imageView.getImageMatrix();
float scale;
final int viewWidth = getWidth() - getPaddingLeft() - getPaddingRight();
final int drawableWidth = drawable.getIntrinsicWidth();
scale = (float) viewWidth / (float) drawableWidth;
matrix.setScale(scale, scale);
setImageMatrix(matrix);

How to add many buttons dynamically giving a line jump before arriving at the edge in Android?

I am trying to add many button into Relativelayout or Linearlayout,
Layout
<Relativelayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
>
</Relativelayout>
then in the class
_ll_layout = (RelativeLayout) findViewById(R.id.container);
I only know how add the button dynamically with code.
JSONArray jsonArray = new JSONArray(tmp.getString("productos"));
Button bt[] = new Button[jsonArray.length()]; // size of product
for (int i = 0; i < jsonArray.length(); i ++){
int padding_40dp = (int) (40 * scale + 0.5f);
int margin_10dp = (int) (10 * scale + 0.5f);
int padding_90dp = (int) (90 * scale + 0.5f);
LinearLayout.LayoutParams params = new Relativelayout.LayoutParams(padding_90dp, padding_40dp);
params.setMargins(margin_10dp, 0 , 0, 0);
bt[i] = new Button(DetalleServicioActivity.this);
bt[i].setText(jsonArray.getJSONObject(i).getString("nombre"));
bt[i].setTag(new TagInfo(jsonArray.getJSONObject(i).getString("id_producto")));
bt[i].setTextColor(Color.parseColor("#FFFFFF"));
bt[i].setBackgroundColor(Color.parseColor("#D8D8D8"));
bt[i].setEnabled(false);
bt[i].setId(Integer.parseInt(jsonArray.getJSONObject(i).getString("id_producto")));
bt[i].setLayoutParams(params);
_ll_layout.addView(bt[i]);
}
but the result is
One on another one, but I need something like this:
Edit
If I use LinearLayout with orientation horizontal and gravity center, this happend
Instead of using Relative Layout or Linear Layout I would rather suggest you to create custom flow layout.Custom flow layout will adjust child views accordingly in rows, and will jump the button in new row according to screen width.
Please have a look here : Flow layout example
Happy Coding :)
Instead of RelativeLayout, make use of LinearLayout with orientation as horizontal and add the button in them at run time
As per your design requirement, make sure you have two linear layouts here.
you can use griedlayout for solved your problem

How to define layout that will work on 720p as well as 1080p screens?

I know I'm supposed to define different layouts for different screens, please read on.
I'm working on the app that will be run an "Android on the stick" plugged to either 720p or 1080p TV and left alone to show some "slide show".
I need something like android:layout_width="n%". I know I can do it with LinearLayout, but the screen is rather complex and such deep nesting is discouraged. I would like to stick to RelativeLayout.
I have designed the layout specifying the metrics in pixels, designing for 1080p and put the layout to layout-tvdpi or layout-xhdpi hoping Android would scale it properly down to 720p. It does not. All artefacts are overblown and overlap.
Is there a way to achieve what I want? Thanks.
As I mentioned in the question, the app I'm working on will be displayed on either 720p or 1080p TV. This allows me to commit the following blasphemy: All view sizes/paddings/margins and text sizes are defined in PX, not DP, not SP. This way I can control the exact screen layout.
Now, contrary to what the documentation made me to believe, Android will not resize any layout element that has size defined in PX. It will only do it for drawables or DP/SP.
Thus I had to resize on my own.
I define the layouts for 1080p. in Activity.onCreate() get the root layout and pass it to:
private static final float RATIO_720_1080 = 720f/1080f;
/**
* If the screen is 720p, the resources will resize
* to fit the screen.
* #param root Root view where the resources are found.
*/
public void resizeViews(ViewGroup root) {
DisplayMetrics metrics = getResources().getDisplayMetrics();
// only resize if 720p
if (metrics.widthPixels != 1280) return;
int childCount = root.getChildCount();
for (int i = 0; i < childCount; i++) {
View v = root.getChildAt(i);
// digg deep
if (v instanceof ViewGroup) {
resizeViews((ViewGroup)v);
}
// do the size
ViewGroup.LayoutParams params = v.getLayoutParams();
// keep the match_parent constants intact
if (params.height > 0) {
params.height = Math.round(params.height * RATIO_720_1080);
}
if (params.width > 0) {
params.width = Math.round(params.width * RATIO_720_1080);
}
if (params instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams marginParams = (ViewGroup.MarginLayoutParams) params;
marginParams.setMargins(Math.round(marginParams.leftMargin * RATIO_720_1080),
Math.round(marginParams.topMargin * RATIO_720_1080),
Math.round(marginParams.rightMargin * RATIO_720_1080),
Math.round(marginParams.bottomMargin * RATIO_720_1080));
}
v.setLayoutParams(params);
v.setPadding(Math.round(v.getPaddingLeft() * RATIO_720_1080),
Math.round(v.getPaddingTop() * RATIO_720_1080),
Math.round(v.getPaddingRight() * RATIO_720_1080),
Math.round(v.getPaddingBottom() * RATIO_720_1080));
// text size
if (v instanceof TextView) {
float size = ((TextView)v).getTextSize();
size *= RATIO_720_1080;
((TextView)v).setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
}
}
}
You can use a layout file like below. Notice the layout_sum attribute in the parent layout, and the layout_weight attribute in each child. The first child will occupy 70% of the parent, and the second child 30%.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:weightSum="100">
<Button
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="70"
android:text="A"/>
<Button
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="30"
android:text="B"/>
</LinearLayout>

Find the position of a bitmap in android imageview when container view scaled centerInside

I have an RelativeLayout that contains a custom ImageView, the scaleType="centerInside", I load in a bitmap (usually smaller than the imageView). How can I get the top/left position of where the bitmap was drawn? I need to be able addView's on top a positions relative to the bitmap.
RelativeLayout view = (RelativeLayout) inflater.inflate(R.layout.scroll_scaled, container, false);
ContentImageView image = (ContentImageView) view.findViewById(R.id.base_page);
Bitmap bm = mInterfaceActivity.getPageImage(mPageNumber);
image.setImageBitmap(bm);`
The layout file scrolled_scaled
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:scaleType="centerInside"
android:id="#+id/base_page"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff00ff00"
android:contentDescription="#string/product_page"
android:src="#android:drawable/ic_menu_report_image" >
</ImageView>
</RelativeLayout>
You'll need to do the math yourself using the bounds of the Drawable.
ImageView test = (ImageView) findViewById(R.id.base_page);
Rect bounds = test.getDrawable().getBounds();
int x = (test.getWidth() - bounds.right) / 2;
int y = (test.getHeight() - bounds.bottom) / 2;
First we calculate the space in the View that is not being used by the image. Then since it is centered the extra space is evenly distributed before and after the image so it is draw half of that length into the View.
These numbers are relative to the location of the View but you can add the views X and Y if you need you.
This method returns the bounds of image inside imageView.
/**
* Helper method to get the bounds of image inside the imageView.
*
* #param imageView the imageView.
* #return bounding rectangle of the image.
*/
public static RectF getImageBounds(ImageView imageView) {
RectF bounds = new RectF();
Drawable drawable = imageView.getDrawable();
if (drawable != null) {
imageView.getImageMatrix().mapRect(bounds, new RectF(drawable.getBounds()));
}
return bounds;
}
UPDATE 2: getX and getY will return 0 if you're using unspecified width and height (e.g. wrap_content). Instead of iv.getX() and iv.getY() replace that with the answer to this question: Getting View's coordinates relative to the root layout then add the bounds of the image to those values.
You can do this by adding the ImageView's position to the top left bound of the drawable inside. Something like this:
ImageView iv = (ImageView)findViewById(R.id.image_view);
Drawable d = iv.getDrawable();
Rect bounds = d.getBounds();
int top = iv.getY() + bounds.top;
int left = iv.getX() + bounds.left;
UPDATE: For images that are scaled, you'll have to multiply the top and left coords by the image scale to get more accurate positioning. You can do that like this:
Matrix m = iv.getImageMatrix();
float[] values = new float[9];
m.getValues(values);
float scaleX = values[Matrix.MSCALE_X];
float scaleY = values[Matrix.MSCALE_Y];
Then you'd have to multiply top by scaleY and the left by scaleX.
Ended up with a two part solution, based on the feedback and a bit of retry.
I created the subviews an added them to the RelativeLayout in "approximate"
position, but as View.INVISIBLE.
I super-classed the RelativeLayout ViewGroup and in the onLayout I walked
the list of child views and put them in the "proper" place as I now
had the RelativeLayout self-aware of its expanded size.
Seems clunky, but it works.
Thanks to all for the suggestions, my solution was taking pieces of everyones advice.

Android button not resizing when put in table programmatically

I need to change the size (specifically the height) of a button in an Android app where the button resides in a table and is created programmatically. I have tried about 20 different approaches and failed miserably. If I create a button outside the table, I can change the size without a problem, but as soon as it goes in a table, the height stays fixed even though I can change the width.
I have tried creating and using, LinearLayout params, ViewGroup layout params, TableLayout params, etc. and setting their heights either via that constructor (e.g WRAP_CONTENT) or using setHeight(). I have also tried simply calling button.setHeight(). I have changed settings in my xml manifest for the table as well. I have used pixel values and Dpi values. All of this has failed. Here is the basics of what I have so far (just showing a call to b.setHeight()):
TableRow row = new TableRow(getApplicationContext());
row.setId(counter);
TextView t = new TextView(getApplicationContext());
t.setText("BLH " + counter);
t.setTextColor(Color.BLACK);
//I have also tried table.getContext() in this constructor...
Button b = new Button(getApplicationContext());
b.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v)
{
//blah blah blah
});
//Convert from pixels to Dpi
final float scale = getResources().getDisplayMetrics().density;
int heightDp = (int) (33 * scale + 0.5f);
int widthDp = (int) (60 * scale + 0.5f);
b.setText(R.string.removeButtonText);
b.setTextSize(12);
b.setTag(counter);
b.setHeight(heightDp);
b.setWidth(widthDp);
b.setId(counter);
counter++;
row.addView(t);
row.addView(b);
// add the TableRow to the TableLayout
table.addView(row,new TableLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
This correctly results in a button being placed in the table, but the height does not change no matter what values I use. In my xml file, here is what the table declaration looks like:
<TableLayout
android:id="#+id/myTable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:stretchColumns="0">
</TableLayout>
I have tried messing with stretchColumns and other settings here; again, to no avail. Does anyone have any idea why I can't change the height of a button added to my table programmatically, but have no problems doing so outside the table? I am sure it is some setting or adjustment that I just haven't found. Any help is appreciated as I am at my wits end. Thanks in advance.
I fixed my problem. Turns out it was an ordering issue. The problem is that I was setting the height before adding the button to the table. This caused the button's LayoutParams to not have any parent as explained here: ViewGroup.getLayoutParams(). Here is what I needed to do (basically, change height after adding the button to the table):
table.addView(row,new TableLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
//now change the height so the buttons aren't so big...
final float scale = getResources().getDisplayMetrics().density;
int heightDp = (int) (33 * scale + 0.5f);
ViewGroup.LayoutParams params = b.getLayoutParams();
params.height = heightDp;
b.setLayoutParams(params);
b.requestLayout();
table.requestLayout();
The last two lines were suggested above by Lawrence. The table and button seemed to update without these. However, it seemed to me to be a good idea to update just in case.
Try this
int heightDp = (int) (33 * scale + 0.5f);
int widthDp = (int) (60 * scale + 0.5f);
ViewGroup.LayoutParams bLp = new ViewGroup.LayoutParams(widthDp,heightDp);
b.setLayoutParams(bLp);
instead of
b.setHeight(heightDp);
b.setWidth(widthDp);
I think you need to call View.requestLayout, this is not done automatically every time you add/remove child views.

Categories

Resources