I created a compound component Box which I want to add to the layout. Box xml:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/linearLayoutForBlock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="#+id/linearLayout1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" android:background="#drawable/screen_background" android:layout_marginLeft="5dp" android:layout_marginTop="5dp">
<ImageButton
android:id="#+id/imageButtonContent"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center_horizontal"
android:scaleType="fitCenter"
android:src="#drawable/beach_bed" android:background="#drawable/buttonbackground" android:clickable="true" android:layout_margin="5dp" android:contentDescription="#string/sample_text"/>
<TextView
android:id="#+id/textViewContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="#string/sample_text"
android:textColor="#color/deep_blue" android:layout_margin="5dp"/>
</LinearLayout>
</merge>
Box class:
public class Box extends LinearLayout {
private TextView textContent;
private ImageView imageContent;
public Box(Context context, AttributeSet attrs) {
super(context, attrs);
((Activity)getContext()).getLayoutInflater().inflate(R.layout.box, this);
setupViewItems();
}
private void setupViewItems() {
textContent = (TextView) findViewById(R.id.textViewContent);
imageContent = (ImageView) findViewById(R.id.imageButtonContent);
}
public void setTextContent(String text) {
this.textContent.setText(text);
}
public void setImageContent(String tag) {
this.imageContent.setContentDescription(tag);
}
}
Everything works if I add the Box to the main xml file like:
<com.mypackage.alexey.Box
android:id="#+id/mybox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
/>
The problem is that I'd like to create many of boxes programmatically something like this: Box mybox= new Box();. How to do it?
I would suggest you also implement the constructors of the LinearLayout that takes only a Context:
public Box(Context context) {
super(context);
}
Then in your Activity, when you want to add a new Box, instantiate the Box class and add it to your layout:
// I assumed you have a LinearLayout to which you want to add your Box
LinearLayout parent = (LinearLayout) findViewById(R.id.parent_id);
//create the Box and added to the parent above
Box theBox = new Box(this); // if you are in an Activity
//some LayoutParams to replicate your xml attributes
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
// set the Orientation for the Box
theBox.setOrientation(LinearLayout.HORIZONTAL);
//add the Box
parent.addView(theBox);
Related
I am new to Android development and feel like this is a really trivial problem, but I cannot word it well enough to find a solution online, so I might as well ask the question here.
My goal is to create a reusable component that is essentially an expandable card like the one described here: https://material.io/design/components/cards.html#behavior.
To do it, I created a custom view that extends a CardView:
public class ExpandableCardView extends CardView {
public ExpandableCardView(Context context) {
super(context);
}
public ExpandableCardView(Context context, AttributeSet attrs) {
super(context, attrs);
// get custom attributes
TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ExpandableCardView, 0, 0);
String heading = array.getString(R.styleable.ExpandableCardView_heading);
array.recycle();
// inflate the layout
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.expandable_card_view, this, true);
// set values
TextView headingTextView = findViewById(R.id.card_heading);
headingTextView.setText(heading.toUpperCase());
// set collapse/expand click listener
ImageView collapseExpandButton = findViewById(R.id.collapse_expand_card_button);
collapseExpandButton.setOnClickListener((View v) -> toggleCardBodyVisibility());
}
private void toggleCardBodyVisibility() {
LinearLayout description = findViewById(R.id.card_body);
ImageView imageButton = findViewById(R.id.collapse_expand_card_button);
if (description.getVisibility() == View.GONE) {
description.setVisibility(View.VISIBLE);
imageButton.setImageResource(R.drawable.ic_arrow_up);
} else {
description.setVisibility(View.GONE);
imageButton.setImageResource(R.drawable.ic_arrow_down);
}
}
}
And the layout:
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/expandable_card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="16dp"
android:animateLayoutChanges="true"
app:cardCornerRadius="4dp">
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/card_header"
android:padding="12dp"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal" >
<TextView
android:id="#+id/card_heading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="#color/colorPrimary"
android:layout_alignParentLeft="true"
android:text="HEADING"/>
<ImageView
android:id="#+id/collapse_expand_card_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
app:srcCompat="#drawable/ic_arrow_down"/>
</RelativeLayout>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/card_body"
android:padding="12dp"
android:layout_marginTop="28dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:visibility="gone" >
</LinearLayout>
</androidx.cardview.widget.CardView>
Ultimately I want to be able to use it like so in my activities, usually multiple instances per activity:
<xx.xyz.yy.customviews.ExpandableCardView
android:id="#+id/card_xyz"
android:layout_width="match_parent"
android:layout_height="match_parent"
custom_xxx:heading="SOME HEADING" >
<SomeView></SomeView>
</xx.xyz.yy.customviews.ExpandableCardView>
Where SomeView is any text, image, layout or another custom view altogether, typically with data bound from the activity.
How do I get it to render SomeView inside the card body? I want to take whatever child structure is defined within the custom view and show it in the card body when it is expanded. Hope I made it easy to understand.
I think that a better approach would be to define the layout that will be inserted into the CardView ("SomeView") in a separate file and reference it with a custom attribute like this:
<xx.xyz.yy.customviews.ExpandableCardView
android:id="#+id/card_xyz"
android:layout_width="match_parent"
android:layout_height="match_parent"
custom_xxx:heading="SOME HEADING"
custom_xxx:expandedView="#layout/some_view"/>
I'll explain my rationale at the end, but let's look at an answer to your question as stated.
What you are probably seeing with your code is SomeView and expandable_card_view appearing all at once in the layout. This is because SomeView is implicitly inflated with the CardView and then expandable_card_view is added through an explicit inflation. Since working with layout XML files directly is difficult, we will let the implicit inflation occur such that the custom CardView just contains SomeView.
We will then remove SomeView from the layout, stash it, and insert expandable_card_view in its place. Once this is done, SomeView will be reinserted into the LinearLayout with the id card_body. All this has to be done after the completion of the initial layout. To get control after the initial layout is complete, we will use ViewTreeObserver.OnGlobalLayoutListener. Here is the updated code. (I have removed a few things to simplify the example.)
ExpandableCardView
public class ExpandableCardView extends CardView {
public ExpandableCardView(Context context) {
super(context);
}
public ExpandableCardView(Context context, AttributeSet attrs) {
super(context, attrs);
// Get control after layout is complete.
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// Remove listener so it won't be called again
getViewTreeObserver().removeOnGlobalLayoutListener(this);
// Get the view we want to insert into the LinearLayut called "card_body" and
// remove it from the custom CardView.
View childView = getChildAt(0);
removeAllViews();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.expandable_card_view, ExpandableCardView.this, true);
// Insert the view into the LinearLayout.
((LinearLayout) findViewById(R.id.card_body)).addView(childView);
// And the rest of the stuff...
TextView headingTextView = findViewById(R.id.card_heading);
headingTextView.setText("THE HEADING");
// set collapse/expand click listener
ImageView collapseExpandButton = findViewById(R.id.collapse_expand_card_button);
collapseExpandButton.setOnClickListener((View v) -> toggleCardBodyVisibility());
}
});
}
private void toggleCardBodyVisibility() {
LinearLayout description = findViewById(R.id.card_body);
ImageView imageButton = findViewById(R.id.collapse_expand_card_button);
if (description.getVisibility() == View.GONE) {
description.setVisibility(View.VISIBLE);
imageButton.setImageResource(R.drawable.ic_arrow_up);
} else {
description.setVisibility(View.GONE);
imageButton.setImageResource(R.drawable.ic_arrow_down);
}
}
}
expandable_card_view.java
The CardView tag is changed to merge to avoid a CardView directly nested within a CardView.
<merge
android:id="#+id/expandable_card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="16dp"
android:animateLayoutChanges="true"
app:cardCornerRadius="4dp">
<RelativeLayout
android:id="#+id/card_header"
android:padding="12dp"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal" >
<TextView
android:id="#+id/card_heading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="#color/colorPrimary"
android:layout_alignParentLeft="true"
android:text="HEADING"/>
<ImageView
android:id="#+id/collapse_expand_card_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
app:srcCompat="#drawable/ic_arrow_down"/>
</RelativeLayout>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/card_body"
android:padding="12dp"
android:layout_marginTop="28dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:visibility="gone" >
</LinearLayout>
</merge>
activity_main.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.customcardview.ExpandableCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/imageView"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_android" />
<TextView
android:id="#+id/childView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Say my name."
android:textSize="12sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/imageView" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.example.customcardview.ExpandableCardView>
</LinearLayout>
So, why do I suggest that you use a custom attribute to include SomeView in the layout as I identified at the beginning? In the way outlined above, SomeView will always be inflated and there is some effort to switch the layout around although SomeView may never be shown. This would be expensive if you have a lot of custom CardViews in a RecyclerView for instance. By using a custom attribute to reference an external layout, you would only need to inflate SomeView when it is being shown and the code would be a lot simpler and easier to understand. Just my two cents and it may not really matter depending upon how you intend to use the custom view.
I've got problem with creating custom view programmatically.
I wrote something like:
public class TimerCardView extends CardView {
private LinearLayout horizontalContentLayout;
private TextView timeTextView;
public TimerCardView(Context context, AttributeSet attrs) {
super(context, attrs);
horizontalContentLayout = new LinearLayout(context);
LinearLayout.LayoutParams horizontalContentXLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
horizontalContentXLayoutParams.gravity = Gravity.CENTER;
horizontalContentLayout.setLayoutParams(horizontalContentXLayoutParams);
this.addView(horizontalContentLayout);
timeTextView = new TextView(context);
timeTextView.setTextColor(Color.parseColor("#A3A3A3"));
timeTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 40);
timeTextView.setText("0:00:00");
this.horizontalContentLayout.addView(timeTextView);
}
}
and in xml I added:
<com.example.paciu.belmondo.ViewsExtends.TimerCardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
card_view:cardCornerRadius="2dp"
card_view:cardElevation="2dp"
android:layout_margin="4dp">
</com.example.paciu.belmondo.ViewsExtends.TimerCardView>
And the problem is that my text view isn't centered on the screen in the layout. It still stays to the left. But when I added firstly to the xml the same (but the whole in xml):
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
card_view:cardCornerRadius="2dp"
card_view:cardElevation="2dp"
android:layout_margin="4dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="40sp"
android:text="sdasdasd"
android:textColor="#AAAAAA" />
</LinearLayout>
</android.support.v7.widget.CardView>
What am I doing wrong? Thank you for your help!
CardView extends the FrameLayout class. The layout_gravity attribute lands on the FrameLayout.LayoutParams, not on the view horizontalContentLayout. For setting gravity like in your XML use something like:
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT, Gravity.CENTER);
this.addView(horizontalContentLayout,params);
I have layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#535353">
<TextView
android:id="#+id/promo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:padding="5dp"
android:layout_weight="1"
/>
<TextView
android:id="#+id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:padding="5dp"
android:layout_weight="1"
/>
</LinearLayout>
and I want to replace promo and title. Set first title, then promo.
I use this snippet.
public class AdView extends LinearLayout{
TextView promo;
public AdView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
LayoutInflater.from(getContext()).inflate(R.layout.view, this);
promo = (TextView) findViewById(R.id.promo);
title = (TextView) findViewById(R.id.title);
}
private void changePosition() {
removeView(promo);
addView(promo);
}
But, I get error: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
So, could you help me? How can I do this?
I am trying to create a icon and a short description below it. I have created a customised View class with a ImageView and a TextView and i integrated it with my xml file, now the imageView and the default text appears in the screen,i don't know how to change the text content of the textview or the source of the imageview.
MyView.java
package sda.jk;
public class MyView extends LinearLayout{
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
LayoutInflater li=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v= li.inflate(R.layout.myview, this);
}
}
myview.xml
<?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" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/ic_launcher"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="my view"/>
</LinearLayout>
main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/sda.jk"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<sda.jk.MyView
android:id="#+id/myView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</sda.jk.MyView>
</LinearLayout>
Step-1 First assign id to image view and text view in myview. xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ImageView
android:id="#+id/myImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/ic_launcher"
/>
<TextView
android:id="#+id/myText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="my view"/>
</LinearLayout>
Step-2
initialize your own view in oncreate method using
MyView mv = (MyView)findViewById(R.id.myView1);
step-3
initilize image view and text view using findViewById method of your own view
TextView textView = (TextView) mv.findViewById(R.id.myText);
ImageView imageView = (ImageView) mv.findViewById(R.id.myImage);
step-4
set text to text view using setText method
textView.setText("Sunil Kumar Sahoo");
set background image to image view using
imageView.setBackgroundResource(R.drawable.ic_launcher);
Your oncreate method will be like the following (from step-2 to step-4)
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
MyView mv = (MyView)findViewById(R.id.myView1);
TextView textView = (TextView) mv.findViewById(R.id.myText);
textView.setText("Sunil Kumar Sahoo");
ImageView imageView = (ImageView) mv.findViewById(R.id.myImage);
imageView.setBackgroundResource(R.drawable.ic_launcher);
}
You write a function in sda.jk.MyView for setting text. For changing text, just call the function with a string parameter
Class MyView extends LinearLayout
{
//...
public void setUserText(String str)
{
textview.setText(str);
}
//...
}
You can add custom attributes by writing a custom attr.xml and loading the attributes in your custom view's constructor. For a full example check this page
http://javawiki.sowas.com/doku.php?id=android:custom-view-attributes
There is a useful feature in TextView called compund drawables, which allows you to incorporate a drawable to be drawn above, below, left or right of your text.
use it like so:
<TextView
android:id="#+id/txt"
android:text="my compound drawable textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="#drawable/xxxxx" />
you can use drawableBottom, drawableLeft etc.. as well.
this way you don't need to implement your own class.
For accessing individual elements from a View hierarchy, use the findViewById method. Refer to http://developer.android.com/reference/android/view/View.html#findViewById%28int%29
You would have to provide identifiers to your view elements in your XML thus:
<?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" >
<ImageView
android:id="#+id/imgView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/ic_launcher" />
<TextView
android:id="#+id/txtView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="my view" />
</LinearLayout>
and refer them from your custom view class thus:
ImageView imageView = (ImageView)v.findViewById(R.id.imgView);
TextView textView = (TextView)v.findViewById(R.id.txtView);
Typically, you would like to keep the views as instance variables (members) so that you can change them from other methods.
I'm new to Android development, and am having a tough time working something out. I want to make a composite view (called SkillDiceGroup) of a TextView, EditText, and SkillDiceButton (which is an extension of the Button class). I have it working when declaring my SkillDiceGroup as pure code, and putting this in my XML layout:
<com.jeremybush.d20.SkillDiceGroup android:id="#+id/skillDiceTest"
android:title="Foobar!"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</com.jeremybush.d20.SkillDiceGroup>
And I have this code:
public class SkillDiceGroup extends LinearLayout
{
// The View components
private TextView mTitle;
private EditText mSkill;
private SkillDiceButton mDice;
public SkillDiceGroup(Context context, AttributeSet attrs)
{
super(context);
this.setOrientation(HORIZONTAL);
mTitle = new TextView(context);
mTitle.setText("foobar");
addView(mTitle, new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT
));
mSkill = new EditText(context);
addView(mSkill, new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT
));
mDice = new SkillDiceButton(context, attrs);
mDice.setText("d20");
addView(mDice, new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT
));
}
private class SkillDiceButton extends DiceButton
{
public SkillDiceButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void onClick(View view)
{
modifier = Integer.parseInt(mSkill.getText().toString());
super.onClick(view);
}
}
}
This works how I want it, but I would like to declare the three items in the xml view on their own. How can I do this?
You should take a look at the extensive documention Android provides regarding xml layouts, for example: declaring layout, common layout objects, and hello views which has an detailed example of each layout type.
Taken directly from the LinearLayout tutorial, a layout with 3 items:
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1">
<TextView
android:text="row one"
android:textSize="15pt"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<TextView
android:text="row two"
android:textSize="15pt"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<TextView
android:text="row three"
android:textSize="15pt"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<TextView
android:text="row four"
android:textSize="15pt"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</LinearLayout>
Just replace the second two text views with an EditText and a SkillDiceButton.
If you wan't to create your own view/widget that 'hides' internal layout structure, and can be reusable in various places - you should read document about creating custom components
also read about tag, and LayoutInflater
But if want to use SkillDiceGroup only once - just create layout as proposed by Mayra