I want to create a custom ImageView with fixed height and width.
It's easy to do with XML, like
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"/>
But how to do it programmatically?
class customView : ImageView {
// code to achieve fixed height and width
}
You will want to use the AppCompat version of what you want to use, such as the AppCompatImageView as layoutParams would then be supported. One thing I also saw wrong: you must specify the context and all of the included attributes in the constructor of the class:
(context : Context, attrs : AttributeSet)
These will be auto assigned by the OS so nothing really needs to be done with them, but they must be there. Here is what I have for you:
class CustomImageView(context: Context,
attrs : AttributeSet) : AppCompatImageView(context, attrs) {
init {
applyDefaults()
}
private fun applyDefaults(){
val height = 130
val width = 300
layoutParams = ViewGroup.LayoutParams(width, height)
}
}
Get the current layout parameters by getting them from the base class, which would be AppCompatImageView. Then set the layout parameters to be the height and width you want as an integer.
Related
I created a test MvxFrameLayout derived class, which I want to draw a child at 0,0 with size 24x24:
public class MyCustomLayout : MvxFrameLayout
{
public MyCustomLayout(Context context, IAttributeSet attrs) : base(context, attrs)
{
}
public MyCustomLayout(Context context, IAttributeSet attrs, IMvxAdapterWithChangedEvent adapter) : base(context, attrs)
{
}
protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
{
if (!changed)
{
return;
}
for (int i = 0; i < this.ChildCount; ++i)
{
var child = this.GetChildAt(i);
child.Layout(0, 0, 24, 24);
}
}
}
Which is used in an activity layout (FirstView.axml) like this:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<hotspotappandroid.droid.views.MyCustomLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
local:MvxBind="ItemsSource Hotspots"
local:MvxItemTemplate="#layout/hotspot" />
</FrameLayout>
The view model which has one item:
public class FirstViewModel : MvxViewModel
{
public string[] Hotspots { get; private set; }
public FirstViewModel()
{
this.Hotspots = new string[] { "A" };
}
}
The hotspot.xml is an ImageView with an image (circle.png) of 24x24:
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="#drawable/circle" />
The problem is the circle image is not being drawn.
If in hotspot.xml I change android:layout_width="fill_parent" and android:layout_height="fill_parent to wrap_content, the image is drawn, but not correctly.
The image is drawn in half. It looks like the image is drawn scaled to double of its size and it's cropped by half (probably due to `child.Layout(0, 0, 24, 24)).
I am not sure what is going on. I see that the child in OnLayout is of type cirrious.mvvmcross.binding.droid.views.MvxListItemView instead of 'ImageView' because that I would have expected. Maybe that has something to do?
The MvxFrameLayout works by inflating a child MvxListItemView for each child. The MvxListItemView itself inherits from FrameLayout and has default layout parameters.
According to the docs - http://developer.android.com/reference/android/widget/FrameLayout.html - this means each child FrameLayout should be sized:
The size of the FrameLayout is the size of its largest child (plus padding), visible or not (if the FrameLayout's parent permits)
Since you are specifying fill_parent for the ImageView inner grandchildren here, then I guess that is what is causing your inner views to be zero-sized.
In order to give your child frames some size, it might be better to give them ImageView sizes directly in the inner child XML, instead of requesting fill_parent
If you do want to return an ImageView directly and to use that as the Child - without the intermediate MvxListItemView then in the latest MvvmCross source I believe you can:
inherit a class MyImageView from ImageView and add an IMvxListItemView implementation
use a custom Adapter which creates and returns MyImageView in an overridden CreateBindableView - https://github.com/MvvmCross/MvvmCross/blob/v3/Cirrious/Cirrious.MvvmCross.Binding.Droid/Views/MvxAdapter.cs#L287
There are several constructors available for defining an ImageView.
For Example
1) public ImageView (Context context)
2) public ImageView (Context context, AttributeSet attrs)
3) public ImageView (Context context, AttributeSet attrs, int defStyle)**
I am confused in using 2nd and 3rd type of constructor.
basically i don't know what to pass in place of AttributeSet.
Kindly provide a coding example.
These constructors are defined in the View documentation. Here is a description of the parameters from View(Context, AttributeSet, int):
Parameters
context The Context the view is running in, through which it can access the current theme, resources, etc.
attrs The attributes of the XML tag that is inflating the view.
defStyle The default style to apply to this view. If 0, no style will be applied (beyond what is included in the theme). This may
either be an attribute resource, whose value will be retrieved from
the current theme, or an explicit style resource.
It's worth noting that you can pass null in place of an AttributeSet if you have no attributes to pass.
In terms of coding the AttributeSet, here's a bit of code I use for a custom TextView class I have:
public EKTextView(Context context, AttributeSet attrs) {
super(context, attrs);
// ...
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LocalTextView);
determineAttrs(context, a);
}
// ...
}
private void determineAttrs(Context c, TypedArray a) {
String font = a.getString(R.styleable.fontName);
if (font != null)
mTypeface = Typeface.createFromAsset(c.getAssets(), "fonts/" + font);
mCaps = a.getBoolean(R.styleable.allCaps, false);
}
As you can see, once you get a TypedArray from the attributes, you can just use its various methods to collect each of the attributes. Other code you may want to review is that of View(Context, AttributeSet, int) or Resources.obtainStyledAttributes(AttributeSet, int[], int, int).
Ways of creating imageView, ImageView with Context
ImageView image= new ImageView(context);
Here when you want set the values like height, width gravity etc you need to set
image.set****();
based on the number of attributes you need to use no of setXXX() methods,.
2.Using Attribute set
you can define set of attributes like height, width etc in your res/values folder in separate xml file, pass the xml file to getXml()
XmlPullParser parser = resources.getXml(yourxmlfilewithattribues);
AttributeSet attributes = Xml.asAttributeSet(parser);
ImageView image=new ImageView(context,attributes);
Here you can also define your custom attributes in your xml . and you can access the by using the methods provided by AttributeSet class example
getAttributeFloatValue(int index, float defaultValue)
//Return the float value of attribute at 'index'
I am building a custom View that contains two standard Views. I have a default style for each contained View, and a custom attribute that lets the user specify a custom style for each contained View. I can get the default vs. custom styles just fine, and pass the right style id as the third parameter of each contained View's constructor. What I am having a hard time doing is generating a ViewGroup.LayoutParams for these contained Views, based on the android:layout_height and android:layout_width in the appropriate style.
It seems like I need to use the ViewGroup.LayoutParams(Context, AttributeSet) constructor, and the AttributeSet docs say that I should get an AttributeSet via
XmlPullParser parser = resources.getXml(myResouce);
AttributeSet attributes = Xml.asAttributeSet(parser);
... but that throws a Resources$NotFoundException with a warning from frameworks/base/libs/utils/ResourceTypes.cpp that Requesting resource %p failed because it is complex.
Hence, my questions, in decreasing order of specificity:
Is there a way to get an XmlPullParser that works with "complex" elements?
Is there some other way to get an AttributeSet that corresponds to a <style> element?
Is there some other way to construct a LayoutParameters that will pay attention to the layout_height and layout_width values in a given style?
static ViewGroup.LayoutParams layoutFromStyle(Context context,
int style) {
TypedArray t = context.getTheme().obtainStyledAttributes(
null,
new int[] { android.R.attr.layout_width,
android.R.attr.layout_height }, style, style);
try {
int w = t
.getLayoutDimension(0, ViewGroup.LayoutParams.WRAP_CONTENT);
int h = t
.getLayoutDimension(1, ViewGroup.LayoutParams.WRAP_CONTENT);
return new ViewGroup.LayoutParams(w, h);
} finally {
t.recycle();
}
}
Is there any way I can get around having to add the layout_width and layout_height parameters to my custom views? I know there are a few built in android views that you don't have to supply those attributes for.
It's not a View's responsibility to decide whether or not it can/should provide these attributes. The parent ViewGroup dictates whether these attributes are mandatory or not. TableRow for instance makes them optional. Other layouts (LinearLayout, FrameLayout, etc.) require these params.
When would you want to not use the height and width parameters? I'm not sure but I think that would cause them to not even show up on the layout?
Look here for reference http://developer.android.com/guide/topics/ui/declaring-layout.html#layout-params
From the same Reference Dave has suggested.
All view groups include a width and height (layout_width and
layout_height), and each view is required to define them. Many
LayoutParams also include optional margins and borders.
So it looks like, you have to.
If reducing common and redundant attributes is what you want, then you should try styling.
Developer guide here.
The problem is that ViewGroup.LayoutParams.setBaseAttributes() uses the strict getLayoutDimension(int, String).
You need to extend whichever LayoutParams you need and override setBaseAttributes.
Inside you can either manually set width and height or use the more lenient getLayoutDimension(int, int). Finally, you'll have to override in your layout class that you are using your own LayoutParams.
#Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
public static class LayoutParams extends FrameLayout.LayoutParams {
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
width = a.getLayoutDimension(widthAttr, WRAP_CONTENT);
height = a.getLayoutDimension(heightAttr, WRAP_CONTENT);
}
}
I have a custom view and I simply wish to access the xml layout value of layout_height.
I am presently getting that information and storing it during onMeasure, but that only happens when the view is first painted. My view is an XY plot and it needs to know its height as early as possible so it can start performing calculations.
The view is on the fourth page of a viewFlipper layout, so the user may not flip to it for a while, but when they do flip to it, I would like the view to already contain data, which requires that I have the height to make the calculations.
Thanks!!!
that work :)... you need to change "android" for "http://schemas.android.com/apk/res/android"
public CustomView(final Context context, AttributeSet attrs) {
super(context, attrs);
String height = attrs.getAttributeValue("http://schemas.android.com/apk/res/android", "layout_height");
//further logic of your choice..
}
You can use this:
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
int[] systemAttrs = {android.R.attr.layout_height};
TypedArray a = context.obtainStyledAttributes(attrs, systemAttrs);
int height = a.getDimensionPixelSize(0, ViewGroup.LayoutParams.WRAP_CONTENT);
a.recycle();
}
From public View(Context context, AttributeSet attrs) constructor docs:
Constructor that is called when
inflating a view from XML. This is
called when a view is being
constructed from an XML file,
supplying attributes that were
specified in the XML file.
So to achieve what you need, provide a constructor to your custom view that takes Attributes as a parameter, i.e.:
public CustomView(final Context context, AttributeSet attrs) {
super(context, attrs);
String height = attrs.getAttributeValue("android", "layout_height");
//further logic of your choice..
}
• Kotlin Version
The answers which are written to this question do not completely cover the issue. Actually, they are completing each other. To sum up the answer, first we should check the getAttributeValue returning value, then if the layout_height defined as dimension values, retrieve it using getDimensionPixelSize:
val layoutHeight = attrs?.getAttributeValue("http://schemas.android.com/apk/res/android", "layout_height")
var height = 0
when {
layoutHeight.equals(ViewGroup.LayoutParams.MATCH_PARENT.toString()) ->
height = ViewGroup.LayoutParams.MATCH_PARENT
layoutHeight.equals(ViewGroup.LayoutParams.WRAP_CONTENT.toString()) ->
height = ViewGroup.LayoutParams.WRAP_CONTENT
else -> context.obtainStyledAttributes(attrs, intArrayOf(android.R.attr.layout_height)).apply {
height = getDimensionPixelSize(0, ViewGroup.LayoutParams.WRAP_CONTENT)
recycle()
}
}
// Further to do something with `height`:
when (height) {
ViewGroup.LayoutParams.MATCH_PARENT -> {
// defined as `MATCH_PARENT`
}
ViewGroup.LayoutParams.WRAP_CONTENT -> {
// defined as `WRAP_CONTENT`
}
in 0 until Int.MAX_VALUE -> {
// defined as dimension values (here in pixels)
}
}