Anchoring SKAnnotation by customView at runtime - android

Running SDK 2.5.1, I'm trying to add my own SKAnnotations with my customView, whose size changes dynamically depending on the name displayed. To do this, I've attached an OnGlobalLayoutListener, to set the annotation's offset accordingly. However, it seems the global listener is not being called at all. I've tried attaching the it to both mapView.getAnnotationView().getView(), to mapView and to the mapHolder of the fragment, with no results. Any solution to allow me to anchor my annotations to the actual location would be appreciated.
final SKAnnotation newDelhi = new SKAnnotation(444);
newDelhi.setMininumZoomLevel(5);
newDelhi.setAnnotationType(SKAnnotation.SK_ANNOTATION_TYPE_MARKER);
SKAnnotationView newDelhiAnnotationView = new SKAnnotationView();
RelativeLayout newDelhiCustomView =
(RelativeLayout) ((LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(
R.layout.annotation_layout, null, false);
newDelhiCustomView.setTag("new delhi");
newDelhiAnnotationView.setView(newDelhiCustomView);
newDelhi.setAnnotationView(newDelhiAnnotationView);
TextView newDelhiTextView = (TextView) newDelhiAnnotationView.getView().findViewById(R.id.annotation_textview);
newDelhiTextView.setText("New Delhi");
SKCoordinate newDelhiLocation = new SKCoordinate(77.179163, 28.619828);
newDelhi.setLocation(newDelhiLocation);
addRedPin(newDelhiLocation, 445);
mapView.addAnnotation(newDelhi, SKAnimationSettings.ANIMATION_NONE);
ViewTreeObserver vto = mapHolder.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
mapHolder.getViewTreeObserver().removeGlobalOnLayoutListener(this);
float width = newDelhi.getAnnotationView().getView().getWidth();
float height = newDelhi.getAnnotationView().getView().getHeight();
newDelhi.setOffset(new SKScreenPoint(-width / 2, height / 2));
mapView.updateAnnotation(newDelhi);
}
});
final SKAnnotation chandigarh = new SKAnnotation(555);
chandigarh.setMininumZoomLevel(5);
chandigarh.setAnnotationType(SKAnnotation.SK_ANNOTATION_TYPE_MARKER);
SKAnnotationView chandigahrAnnotationView= new SKAnnotationView();
RelativeLayout chandigarhCustomView =
new RelativeLayout(getActivity());
chandigarhCustomView.setTag("chandigarh");
TextView chandigarhTextView = new TextView(getActivity());
chandigarhTextView.setText("Chandigarh");
chandigarhCustomView.addView(chandigarhTextView);
chandigahrAnnotationView.setView(chandigarhCustomView);
chandigarh.setAnnotationView(chandigahrAnnotationView);
SKCoordinate chandigarhLocation = new SKCoordinate(76.778867,30.736094);
chandigarh.setLocation(chandigarhLocation);
mapView.addAnnotation(chandigarh, SKAnimationSettings.ANIMATION_NONE);
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() { mapHolder.getViewTreeObserver().removeGlobalOnLayoutListener(this);
float width = chandigarh.getAnnotationView().getView().getWidth();
float height = chandigarh.getAnnotationView().getView().getHeight();
chandigarh.setOffset(new SKScreenPoint(-width / 2, height / 2));
mapView.updateAnnotation(newDelhi);
}
});
addRedPin(chandigarhLocation, 556);
Here is a screenshot of how the annotations wrongly appear (actual locations are red pins):

Related

i m using 4 imageview in relativelayout and i want to make dynamically. u can see screenshot below

i m using 4 imageview in relativelayout and that images size equal to each other in relative layout but want to make dynamically but i m not getting
Heading
final ImageView profile_img1 = (ImageView) view.findViewById(R.id.profile_img1);
final ImageView profile_img2 = view.findViewById(R.id.profile_img2);
Display display = activity.getWindowManager().getDefaultDisplay();
Point point = new Point();
display.getSize(point);
int width = point.x;
final double margin_15 = width * 0.15;
final RelativeLayout relativeLayout=view.findViewById(R.id.relative_profile);
RelativeLayout.LayoutParams parms = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
parms.setMargins((int) margin_15, 0, (int) margin_15, 0);
relativeLayout.setLayoutParams(parms);
// Toast.makeText(activity,width+"",Toast.LENGTH_SHORT).show();
relativeLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
relativeLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
width_relativelayout = relativeLayout.getMeasuredWidth();
h_relativelayout = relativeLayout.getMeasuredHeight();
// Toast.makeText(activity,width_linearlayout+"===",Toast.LENGTH_SHORT).show();
RelativeLayout.LayoutParams parms_img = new RelativeLayout.LayoutParams( width_relativelayout/2,350);
parms_img.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
profile_img1.setLayoutParams(parms_img);
RelativeLayout.LayoutParams parms_img2 = new RelativeLayout.LayoutParams(width_relativelayout/2,300);
parms_img.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
profile_img1.setLayoutParams(parms_img2);
profile_img2.setLayoutParams(parms_img2);
}
});
You can use RecyclerView and Adapter for same.
and Set GridLayoutManager with number count to show, like 2 for two image view horizontally.
Or you can ArrayList of ImageView.

Set X and Y of a view onclick not changing position

I am trying to programatically change the position of a TextView. I create the TextView in code and display it. Everything is working however the TextView stay in the top left corner of the screen regardless of and changes to setX or setY on it.
This is my code.
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
textView = new TextView(MainActivity.this);
textView.setText("Test");
textView.setPadding(10, 10, 10, 10);
RelativeLayout Screen = (RelativeLayout) findViewById(R.id.screenRelativeLayout);
Typeface face=Typeface.createFromAsset(getAssets(),"fonts/shablagooital.ttf");
textView.setTypeface(face);
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int screenwidth = size.x;
int screenheight = size.y;
Random randdisp = new Random();
int randscreenwidth = randdisp.nextInt(screenwidth) + 1;
int randscreenheight = randdisp.nextInt(screenheight) + 1;
textView.setX(200);
textView.setY(200);
Screen.addView(textView);
Thanks for your help.
Nicholas
You can use LayoutParams:
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
params.setMargins(200, 200, 0, 0);
Screen.addView(textView, params);
Maybe set X and Y AFTER adding the TextView to the Screen.

RelativeLayout align parent bottom does not align at bottom

I have a custom circular layout using a relative layout:
public class CircularLayout extends RelativeLayout implements OnDragListener {
private DropCallback onDrop = null;
private ImageButton imageButton = null;
private ImageView imageViewBackgroundWave = null;
private int radius = -1;
private double step = -1;
private double angle = -1;
private static final int CENTER_ID = 111;
public CircularLayout(Context context, DropCallback onDrop, int radius, List<View> views) {
super(context);
this.onDrop = onDrop;
this.radius = radius;
this.step = (2 * Math.PI) / views.size();
this.initView(context, views);
}
private void initView(Context context, List<View> views) {
RelativeLayout.LayoutParams layoutParamsView = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
this.setLayoutParams(layoutParamsView);
RelativeLayout.LayoutParams layoutParamsImageview = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParamsImageview.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
layoutParamsImageview.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
this.imageViewBackgroundWave = new ImageView(this.getContext());
this.imageViewBackgroundWave.setLayoutParams(layoutParamsImageview);
this.imageViewBackgroundWave.setImageDrawable(this.getResources().getDrawable(R.drawable.background_wave));
this.addView(this.imageViewBackgroundWave);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
this.imageButton = new ImageButton(context);
this.imageButton.setId(CENTER_ID);
this.imageButton.setLayoutParams(layoutParams);
this.imageButton.setImageResource(R.drawable.ic_power_on);
this.imageButton.getBackground().setAlpha(0);
this.imageButton.setOnDragListener(this);
this.addView(this.imageButton);
for(View view : views) {
this.addView(this.placeView(view));
}
}
private View placeView(View view) {
view.measure(0, 0);
this.imageButton.measure(0, 0);
int x = (int)((view.getMeasuredWidth() / 2) + this.radius * Math.cos(this.angle));
int y = (int)((view.getMeasuredHeight() / 2) + this.radius * Math.sin(this.angle));
this.angle += this.step;
int deltaX = view.getMeasuredWidth();
int deltaY = view.getMeasuredHeight();
int deltaImageX = this.imageButton.getMeasuredWidth() / 2;
int deltaImageY = this.imageButton.getMeasuredHeight() / 2;
int xToDraw = ((x - deltaX) - deltaImageX);
int yToDraw = ((y - deltaY) - deltaImageY);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.ABOVE, CENTER_ID);
layoutParams.addRule(RelativeLayout.RIGHT_OF, CENTER_ID);
layoutParams.setMargins(xToDraw, 0, 0, yToDraw);
view.setLayoutParams(layoutParams);
return view;
}
#Override
public boolean onDrag(View view, DragEvent event) {
return this.onDrop.onDrop(view, event);
}
}
Unfortunately the imageview (imageViewBackgroundWave) is not aligning at the bottom. It aligns a little bit higher:
So the question is: how can I align my imageview to the bottom of the screen?
The image is exactly as high and as wide as the blue stripe. there is no
padding or white color in it. Its just the blue stripe shown in the picture above.
EDIT:
The background_wave.png:
I use this custom layout in my MenuFragment and call it with the following code:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// ... init the imagebuttons in the list of views
this.circleView = new CircularLayout(this.getActivity(), this, 250, views);
this.circleView.setOnDragListener(this);
this.circleView.setBackground(this.getResources().getDrawable(R.drawable.background));
return this.circleView;
}
You can get the ImageView to stick to the bottom by adding this line of code:
imageView.setScaleType(ScaleType.FIT_END);
The reason for the weirdness is that the ImageView bounds are calculated by the layout engine first, then the image inside the ImageView is scaled to fit the allocated area according to the scale type.
Using WRAP_CONTENT for the ImageView allocates an area based on the size of the unscaled image bitmap - even if it is larger than the screen. Your background_wave.png file is wider than the screen, so a larger area than is needed is allocated. Then afterwards when the image gets fitted inside the layout area using FIT_CENTER it gets shrunk down and centered, so you end up up with white space above and below it.
You can verify this by resizing your background_wave.png to be 1/4 the size: it should align the bottom even without the above code change.
You are setting width and height twice with RelativeLayout.LayoutParams.WRAP_CONTENT and RelativeLayout.ALIGN_PARENT_BOTTOM
Try this:
private void initView(Context context) {
RelativeLayout.LayoutParams layoutParamsView = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
this.setLayoutParams(layoutParamsView);
RelativeLayout.LayoutParams layoutParamsImageview = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
this.imageView = new ImageView(this.getContext());
this.imageView.setLayoutParams(layoutParamsImageview);
this.imageView.setImageDrawable(this.getResources().getDrawable(R.drawable.background_wave));
this.addView(this.imageView);
}
I also recommend you to use visual designer with XML to test layout's rules and parameters, then, if you need it, you can set that rules in code to get the same result.

Difference in height of root Layout calculated from two methods

I am calculating the height of a root Layout ( RelativeLayout) with height and width as 'fill_parent' using this method and it returns 690
final RelativeLayout rootLayout=(RelativeLayout)findViewById(R.id.root_layout);
ViewTreeObserver vto = rootLayout.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
rootLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
mRootLayoutHeight=rootLayout.getHeight();
Toast.makeText(MyActivity.this,""+mRootLayoutHeight,Toast.LENGTH_LONG).show();
}
});
This layout has a Listview so i calculate the calculate the 'bottom' of individual rows using this
for(int i = 0; i <= month_listview.getLastVisiblePosition() - month_listview.getFirstVisiblePosition(); i++){
View v=month_listview.getChildAt(i);
if(v!=null) {
Rect rectf = new Rect();
v.getGlobalVisibleRect(rectf);
Log.d("bottom :", String.valueOf(rectf.bottom));
}
}
The bottom of the last row should come as 690 but its coming as 776. Why is there a difference?

Set the absolute position of a view

Is it possible to set the absolute position of a view in Android? (I know that there is an AbsoluteLayout, but it's deprecated...)
For example, if I have a 240x320px screen, how could I add an ImageView which is 20x20px such that its center is at the position (100,100)?
You can use RelativeLayout. Let's say you wanted a 30x40 ImageView at position (50,60) inside your layout. Somewhere in your activity:
// Some existing RelativeLayout from your layout xml
RelativeLayout rl = (RelativeLayout) findViewById(R.id.my_relative_layout);
ImageView iv = new ImageView(this);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(30, 40);
params.leftMargin = 50;
params.topMargin = 60;
rl.addView(iv, params);
More examples:
Places two 30x40 ImageViews (one yellow, one red) at (50,60) and (80,90), respectively:
RelativeLayout rl = (RelativeLayout) findViewById(R.id.my_relative_layout);
ImageView iv;
RelativeLayout.LayoutParams params;
iv = new ImageView(this);
iv.setBackgroundColor(Color.YELLOW);
params = new RelativeLayout.LayoutParams(30, 40);
params.leftMargin = 50;
params.topMargin = 60;
rl.addView(iv, params);
iv = new ImageView(this);
iv.setBackgroundColor(Color.RED);
params = new RelativeLayout.LayoutParams(30, 40);
params.leftMargin = 80;
params.topMargin = 90;
rl.addView(iv, params);
Places one 30x40 yellow ImageView at (50,60) and another 30x40 red ImageView <80,90> relative to the yellow ImageView:
RelativeLayout rl = (RelativeLayout) findViewById(R.id.my_relative_layout);
ImageView iv;
RelativeLayout.LayoutParams params;
int yellow_iv_id = 123; // Some arbitrary ID value.
iv = new ImageView(this);
iv.setId(yellow_iv_id);
iv.setBackgroundColor(Color.YELLOW);
params = new RelativeLayout.LayoutParams(30, 40);
params.leftMargin = 50;
params.topMargin = 60;
rl.addView(iv, params);
iv = new ImageView(this);
iv.setBackgroundColor(Color.RED);
params = new RelativeLayout.LayoutParams(30, 40);
params.leftMargin = 80;
params.topMargin = 90;
// This line defines how params.leftMargin and params.topMargin are interpreted.
// In this case, "<80,90>" means <80,90> to the right of the yellow ImageView.
params.addRule(RelativeLayout.RIGHT_OF, yellow_iv_id);
rl.addView(iv, params);
In general, you can add a View in a specific position using a FrameLayout as container by specifying the leftMargin and topMargin attributes.
The following example will place a 20x20px ImageView at position (100,200) using a FrameLayout as fullscreen container:
XML
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/root"
android:background="#33AAFF"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</FrameLayout>
Activity / Fragment / Custom view
//...
FrameLayout root = (FrameLayout)findViewById(R.id.root);
ImageView img = new ImageView(this);
img.setBackgroundColor(Color.RED);
//..load something inside the ImageView, we just set the background color
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(20, 20);
params.leftMargin = 100;
params.topMargin = 200;
root.addView(img, params);
//...
This will do the trick because margins can be used as absolute (X,Y) coordinates without a RelativeLayout:
Just to add to Andy Zhang's answer above, if you want to, you can give param to rl.addView, then make changes to it later, so:
params = new RelativeLayout.LayoutParams(30, 40);
params.leftMargin = 50;
params.topMargin = 60;
rl.addView(iv, params);
Could equally well be written as:
params = new RelativeLayout.LayoutParams(30, 40);
rl.addView(iv, params);
params.leftMargin = 50;
params.topMargin = 60;
So if you retain the params variable, you can change the layout of iv at any time after adding it to rl.
A more cleaner and dynamic way without hardcoding any pixel values in the code.
I wanted to position a dialog (which I inflate on the fly) exactly below a clicked button.
and solved it this way :
// get the yoffset of the position where your View has to be placed
final int yoffset = < calculate the position of the view >
// position using top margin
if(myView.getLayoutParams() instanceof MarginLayoutParams) {
((MarginLayoutParams) myView.getLayoutParams()).topMargin = yOffset;
}
However you have to make sure the parent layout of myView is an instance of RelativeLayout.
more complete code :
// identify the button
final Button clickedButton = <... code to find the button here ...>
// inflate the dialog - the following style preserves xml layout params
final View floatingDialog =
this.getLayoutInflater().inflate(R.layout.floating_dialog,
this.floatingDialogContainer, false);
this.floatingDialogContainer.addView(floatingDialog);
// get the buttons position
final int[] buttonPos = new int[2];
clickedButton.getLocationOnScreen(buttonPos);
final int yOffset = buttonPos[1] + clickedButton.getHeight();
// position using top margin
if(floatingDialog.getLayoutParams() instanceof MarginLayoutParams) {
((MarginLayoutParams) floatingDialog.getLayoutParams()).topMargin = yOffset;
}
This way you can still expect the target view to adjust to any layout parameters set using layout XML files, instead of hardcoding those pixels/dps in your Java code.
Just in case it may help somebody, you may also try this animator ViewPropertyAnimator as below
myView.animate().x(50f).y(100f);
myView.animate().translateX(pixelInScreen)
Note: This pixel is not relative to the view. This pixel is the pixel
position in the screen.
credits to bpr10 answer
Place any view on your desire X & Y point
layout file
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.test.MainActivity" >
<AbsoluteLayout
android:id="#+id/absolute"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<RelativeLayout
android:id="#+id/rlParent"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="#+id/img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/btn_blue_matte" />
</RelativeLayout>
</AbsoluteLayout>
</RelativeLayout>
Java Class
public class MainActivity extends Activity {
private RelativeLayout rlParent;
private int width = 100, height = 150, x = 20, y= 50;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AbsoluteLayout.LayoutParams param = new AbsoluteLayout.LayoutParams(width, height, x, y);
rlParent = (RelativeLayout)findViewById(R.id.rlParent);
rlParent.setLayoutParams(param);
}
}
Done
Try below code to set view on specific location :-
TextView textView = new TextView(getActivity());
textView.setId(R.id.overflowCount);
textView.setText(count + "");
textView.setGravity(Gravity.CENTER);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
textView.setTextColor(getActivity().getResources().getColor(R.color.white));
textView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// to handle click
}
});
// set background
textView.setBackgroundResource(R.drawable.overflow_menu_badge_bg);
// set apear
textView.animate()
.scaleXBy(.15f)
.scaleYBy(.15f)
.setDuration(700)
.alpha(1)
.setInterpolator(new BounceInterpolator()).start();
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
layoutParams.topMargin = 100; // margin in pixels, not dps
layoutParams.leftMargin = 100; // margin in pixels, not dps
textView.setLayoutParams(layoutParams);
// add into my parent view
mainFrameLaout.addView(textView);
My code for Xamarin,
I am using FrameLayout for this purpose and following is my code:
List<object> content = new List<object>();
object aWebView = new {ContentType="web",Width="300", Height = "300",X="10",Y="30",ContentUrl="http://www.google.com" };
content.Add(aWebView);
object aWebView2 = new { ContentType = "image", Width = "300", Height = "300", X = "20", Y = "40", ContentUrl = "https://www.nasa.gov/sites/default/files/styles/image_card_4x3_ratio/public/thumbnails/image/leisa_christmas_false_color.png?itok=Jxf0IlS4" };
content.Add(aWebView2);
FrameLayout myLayout = (FrameLayout)FindViewById(Resource.Id.frameLayout1);
foreach (object item in content)
{
string contentType = item.GetType().GetProperty("ContentType").GetValue(item, null).ToString();
FrameLayout.LayoutParams param = new FrameLayout.LayoutParams(Convert.ToInt32(item.GetType().GetProperty("Width").GetValue(item, null).ToString()), Convert.ToInt32(item.GetType().GetProperty("Height").GetValue(item, null).ToString()));
param.LeftMargin = Convert.ToInt32(item.GetType().GetProperty("X").GetValue(item, null).ToString());
param.TopMargin = Convert.ToInt32(item.GetType().GetProperty("Y").GetValue(item, null).ToString());
switch (contentType) {
case "web":{
WebView webview = new WebView(this);
//webview.hei;
myLayout.AddView(webview, param);
webview.SetWebViewClient(new WebViewClient());
webview.LoadUrl(item.GetType().GetProperty("ContentUrl").GetValue(item, null).ToString());
break;
}
case "image":
{
ImageView imageview = new ImageView(this);
//webview.hei;
myLayout.AddView(imageview, param);
var imageBitmap = GetImageBitmapFromUrl("https://www.nasa.gov/sites/default/files/styles/image_card_4x3_ratio/public/thumbnails/image/leisa_christmas_false_color.png?itok=Jxf0IlS4");
imageview.SetImageBitmap(imageBitmap);
break;
}
}
}
It was useful for me because I needed the property of view to overlap each other on basis of their appearance, e.g the views get stacked one above other.

Categories

Resources