Pass Attributes before CustomView inflation in Fragment based on holder Activity - android

I have a Fragment which includes a custom/compound view in layout.
<app.view.ControlLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:id="#+id/controlLayout"
android:layout_width="match_parent"
android:layout_height="#dimen/fragment_control_size"
android:layout_marginBottom="#dimen/offset_distance"
custom:horizontal="true" />
Fragment has been attached to different activities in layouts, such as:
<fragment
android:id="#+id/controlLayout"
android:name="app.controllers.ControlFragment"
android:layout_width="match_parent"
android:layout_height="#dimen/fragment_control_size"
android:layout_alignParentBottom="true"
android:layout_marginBottom="#dimen/offset_distance"
tools:layout="#layout/fragment_control" />
The custom view has following attributes:
<resources>
<declare-styleable name="ControlLayout">
<attr name="horizontal" format="boolean" />
<attr name="callBtnVisibility" format="boolean" />
</declare-styleable>
</resources>
Based on to which Activity my Control Fragment is attached, I want to pass callBtnVisibility attribute to my custom view. One solution is handle the login in the code without using attributes:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_control, container, false);
if (getActivity() instanceof MyActivity) {
((ControlLayout) view.findViewById(R.id.controlLayout)).hideCallButton();
}
I wonder if I can somehow manage it without using following login. So that before controlLayout get inflated in the fragment, I can pass the attribute to it, and handle the logic in Constructor or init method of custom view:
public ControlLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs, defStyle);
}
private void init(AttributeSet attrs, int defStyle) {
final TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.ControlLayout,
0, defStyle);
final boolean horizontal = a.getBoolean(R.styleable.ControlLayout_horizontal, false);
final boolean callButtonIsVisible = a.getBoolean(
R.styleable.ControlLayout_callBtnVisibility, true);
a.recycle();
if(callButtonIsVisible) {
mCallButton = new Button(getContext());
mCallButton.setId(R.id.button_call);
...

onInflated in fragment lifecycle which is called before onAttach() can solve this issue.

Related

Android: How to initialise a custom View in code and connect it to an existing xml view

I have my own custom view class like below:
public class speak extends ConstraintLayout implements View.OnClickListener {
private TextView text;
private ImageView img;
public speak(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.speak,
0, 0);
try {
text = findViewById(a.getResourceId(R.styleable.speak_text,0));
img = findViewById(a.getResourceId(R.styleable.speak_img, 0));
} finally {
a.recycle();
}
}
}
With this I created attr.xml where I put this resource:
<declare-styleable name="speak">
<attr name="text" format="string"/>
<attr name="img" format="reference"/>
</declare-styleable>
and I also created my own layout xml of how the custom view should look. This way I can add multiple views easily in activity_main.xml. I started by adding one view in activity_main.xml. But now I want to link it to the custom view class in onCreate. what I am doing is this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
speak sl = findViewById(R.id.ss);
sl.seImage(getResources().getIdentifier("com.example.app:drawable/img.png", null, null));
}
where setImage is my own setter for the components in the customView. However, this is returning a null pointer exception which I think I understand since I am not creating a new speak Object and passing attributes in the constructor and so on.
create a new view object but at the same time pass it the xml element which sets its position on screen apart form other stuff.
I am also not sure what should I pass to AttributeSet in the constructor.
udpate
this is the error:
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.ImageView.setImageResource(int)' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
This is because my setter contains img.SetImageResource(int) to get a drawable Id and set te image to that image in drawable folder
Please check GoEditText I have used:
public class GoEditText extends FrameLayout {
private TextInputLayout inputLayout;
private AppCompatEditText editText;
public GoEditText(Context context) {
super(context);
init(context, null);
}
public GoEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
this.context = context;
LayoutInflater.from(context).inflate(R.layout.go_edit_text, this, true);
inputLayout = findViewById(R.id.inputLayout);
editText = findViewById(R.id.editText);
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.GoEditText, -1, 0);
int edtType = typedArray.getInt(R.styleable.GoEditText_edtTextType, 0);
boolean isEditable = typedArray.getBoolean(
R.styleable.GoEditText_isEditAble, true);
}
}
Below is xml go_edit_text.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.TextInputLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/inputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:passwordToggleDrawable="#null">
<android.support.v7.widget.AppCompatEditText
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="#style/CJEditText"
android:id="#+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="5dp"
app:backgroundTint="#color/gray_3" />
</android.support.design.widget.TextInputLayout>
Now you can use it directly in acitivity.xml as
<com.package.name.component.GoEditText
android:id="#+id/myid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:edtTextType="selection"
app:errorMsg="#string/error_employee_type"
app:hint="#string/hint_employee_type" />
Now in onCreate
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
GoEditText edt= findViewById(R.id.myId);
edt.editText.setText("hello");
}
Or you can create a getter method in CustomView for say edittext( imageView in your case)

Android XML layout - Setting an attribute to the value of another attribute of a widget

I would like to set a custom attribute (in this case, app:unfocusColor) to the value of another attribute (e.g app:cardBackgroundColor), is this possible?
I try the below method, but has error
<android.support.v7.widget.CardView
.....
app:cardBackgroundColor="#android:color/holo_blue_bright"
app:unfocusColor="#{app:cardBackgroundColor}"
>
yes you can!
for example
create res/valuesmyattr.xml
use this code as content
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="myatt">
<attr name="viewName" format="string"/>
</declare-styleable>
</resources>
in your activity use this code
#Override
protected void onCreate(Bundle savedInstanceState) {
getLayoutInflater().setFactory(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
int id =attrs.getAttributeResourceValue("http://schemas.android.com/apk/res/android","id",-1);
if (id == R.id.specialViewId) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.myatt);
String viewName = typedArray.getString(R.styleable.myatt_viewName);
}
return super.onCreateView(parent, name, context, attrs);
}
and your view should lookalike this
<TextView
app:number="1"
android:id="#+id/specialViewId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="MissingPrefix"></TextView>
finally if you want use this with out overriding activity you can use LayourInflaterFactory interface

Bug in layout when I use properties from attrs.xml

I am trying to use two properties from attrs.xml such as: content and handle. When I use them while building the layout the view goes away. I have been trying the bug for 3 days and nothing worked.
Help is much appreciated!
Code in Context
<hh.hhh.app.android.hhhh.widget.ExpandablePanel
android:id="#+id/expandablePanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:handle="#+id/expandButton"
app:content="#+id/listViewProduct"
app:collapsedHeight="50dip"
app:animationDuration="25">
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/listViewProduct"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/expandButton"/>
</hh.hhh.app.android.hhhh.widget.ExpandablePanel>
attrs.xml code
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ExpandablePanel">
<attr name="handle" format="reference" />
<attr name="content" format="reference" />
<attr name="collapsedHeight" format="dimension"/>
<attr name="animationDuration" format="integer"/>
</declare-styleable>
</resources>
I am sharing my own custom class as an example. So for all custom function you need to first check if it is in EditMode or not. isInEditMode() tells us is the layout is being render by IDE preview or by phone. IDE preview is not so much powerful to run our custom codes, so it is recommended to do not run our custom while rendering by preview. I am attaching my custom class to just understand the exact scenario.
public class DynamicImageView extends ImageView {
static DynamicImageTypeListener masterImageTypeListener;
DynamicImageTypeListener imageTypeListener;
int imageResourceArray;
public DynamicImageView(Context context, AttributeSet attr, int defStyle) {
super(context, attr, defStyle);
initValues(attr, defStyle);
}
public DynamicImageView(Context context, AttributeSet attr) {
super(context, attr);
initValues(attr, 0);
}
public DynamicImageView(Context context) {
super(context);
}
public static void setMasterImageTypeListener(DynamicImageTypeListener masterImageTypeListener) {
DynamicImageView.masterImageTypeListener = masterImageTypeListener;
}
public void setImageTypeListener(DynamicImageTypeListener imageTypeListener) {
this.imageTypeListener = imageTypeListener;
}
private void initValues(AttributeSet attr, int defStyle) {
if (!isInEditMode()) { //This code will run only on phone not by preview.
TypedArray a = getContext().obtainStyledAttributes(attr, R.styleable.DynamicImageView, defStyle, 0);
imageResourceArray = a.getResourceId(0, 0);
updateImage();
a.recycle();
}
}
public void updateImage() {
TypedArray imgs = getResources().obtainTypedArray(imageResourceArray);
int imageIndex = imageTypeListener != null ? imageTypeListener.getImageType(getContext()) : (masterImageTypeListener != null ? masterImageTypeListener.getImageType(getContext()) : 0);
setImageResource(imgs.getResourceId(imageIndex, -1));
}
}
Hope it will help :)

Custom xml attribute to a #layout reference

I would like to make a custom view with own xml attributes. I would like to specify a header layout that will be inflated in my custom xml view, something like this:
<com.example.MyCustomWidget
android:layout_width="match_parent"
android:layout_height="match_parent"
app:headerLayout="#layout/my_header"
/>
Is that possible and how to i get the layout resource from TypedArray?
So at the end I would like to do something like this:
class MyCustomWidget extends FrameLayout {
public ProfileTabLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ProfileTabLayout);
int headerLayout = a.getLayout(R.styleable.MyCustomView_headerLayout, 0); // There is no such method
a.recycle();
LayoutInflater.from(context)
.inflate(headerLayout, this, true);
}
}
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyCustomWidget">
<attr name="headerLayout" format="reference" />
</declare-styleable>
</resources>
First you have to make your custom field. Yo do this by adding code below to res/values/attrs.xml
<declare-styleable name="MyCustomView">
<attr name="headerLayout" format="reference" />
</declare-styleable>
Then in your custom view you can get this value in constructor
public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
...
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.MyCustomView,
defStyle, 0
);
try {
int headerLayout = a.getResourceId(R.styleable.MyCustomView_headerLayout, 0);
} finally {
a.recycle();
}
...
}
From here on you can inflate headerLayout with LayoutInflater

Default attribute values for my custom view (inherited from LinearLayout)

I have a custom layout
public class PersonView extends LinearLayout{
public PersonView(Context context, AttributeSet attrs) {
super(context, attrs);
initUi(context);
}
public PersonView(Context context) {
super(context);
initUi(context);
}
private void initUi(Context context){
LayoutInflater.from(context).inflate(R.layout.person_view, this, true);
profilePicture = (ImageView)findViewById(R.id.profile_picture);
...
}
Layout is defined in xml
<merge xmlns:android="http://schemas.android.com/apk/res/android">
...
Then I use it in other layout
Case 1
// In this case android:layout_margin is specified so it should be used
<my.package.PersonView android:layout_margin="10dp" .../>
Case 2
// In this case android:layout_margin is NOT specified so I want for my PersonView some default value should be used (say 5pt)
<my.package.PersonView .../>
What should I do for my custom layout PersonView to achieve Case 2?
Similar problem has been solved at https://stackoverflow.com/a/25982512/3554436
Add android:layout_margin to attrs.xml
<declare-styleable name="PersonView">
...
<attr name="android:layout_margin" />
...
</declare-styleable>
Access the attribute like other custom attributes:
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PersonView);
float margin = a.getDimension(R.styleable.PersonView_android_layout_margin, 0);
...
boolean hasMargin = a.hasValue(R.styleable.PersonView_android_layout_margin);

Categories

Resources