I have a project where I need to programmatically create a TextView and place it inside a custom ViewGroup. When I instantiate the textView, I set its layoutParams, text, gravity etc, then add it to the ViewGroup. Then in the onLayout() method of the ViewGroup i try to position it (as recommended by google guidelines).
Everything is fine EXCEPT for the textView's text gravity. Only center_horizontal seems to work. The entire problem can be boiled down to a few lines of code as below:
public class Custom extends ViewGroup {
TextView mTv;
public Custom(Context context, AttributeSet attrs) {
super(context, attrs);
mTv = new TextView(context);
mTv.setText("can't touch this");
mTv.setGravity(Gravity.CENTER);
ViewGroup.LayoutParams params=new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
mTv.setBackgroundColor(context.getResources().getColor(R.color.paleblue));
this.addView(mTv,params);
}
public Custom(Context context) {
this(context,null);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mTv.layout(l,t,r,b);
}}
And the XML code:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.apps.renegade.testbook.Custom
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
The text should be in the center of the screen. Can anyone explain why is this happening? Thanks
If you want to override a ViewGroup, check this, this is the official document which shows you how to extend a ViewGroup.
Related
I want to implement custom ViewGroup in my case derived from FrameLayout but I want all child views added from xml to be added not directly into this view but in FrameLayout contained in this custom ViewGroup.
Let me show example to make it clear.
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="#+id/frame_layout_child_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<FrameLayout
android:id="#+id/frame_layout_top"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</merge>
And I want to redirect adding all child view to FrameLayout with id frame_layout_child_container.
So of course I overrode methods addView() like this
#Override
public void addView(View child) {
this.mFrameLayoutChildViewsContainer.addView(child);
}
But for sure this doesn't work because for this time mFrameLayoutChildViewsContainer is not added to the root custom view.
My idea is always keep some view on on the top in this container frame_layout_top and all child views added into custom component should go to frame_layout_child_container
Example of using custom view
<CustomFrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
</CustomFrameLayout>
So in this case TextView should be added to the frame_layout_child_container
Is it possible to delegate adding all views into child ViewGroup like I described.
I have other ideas like using bringToFront() method every time view is added to keep them in correct z-axis order or for example when view is added, save it to array and than after inflating custom view add all views to this child FrameLayout
Suggest what to do in this case in order not to hit performance with reinflating all layout every time new view is added, if it is possible to implement in other way.
Views inflated from a layout - like your example TextView - are not added to their parent ViewGroup with addView(View child), which is why overriding just that method didn't work for you. You want to override addView(View child, int index, ViewGroup.LayoutParams params), which all of the other addView() overloads end up calling.
In that method, check if the child being added is one of your two special FrameLayouts. If it is, let the super class handle the add. Otherwise, add the child to your container FrameLayout.
public class CustomFrameLayout extends FrameLayout {
private final FrameLayout topLayout;
private final FrameLayout containerLayout;
...
public CustomFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.custom, this, true);
topLayout = (FrameLayout) findViewById(R.id.frame_layout_top);
containerLayout = (FrameLayout) findViewById(R.id.frame_layout_child_container);
}
#Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
final int id = child.getId();
if (id == R.id.frame_layout_top || id == R.id.frame_layout_child_container) {
super.addView(child, index, params);
}
else {
containerLayout.addView(child, index, params);
}
}
}
I want to implement custom ViewGroup in my case derived from FrameLayout but I want all child views added from xml to be added not directly into this view but in FrameLayout contained in this custom ViewGroup.
Let me show example to make it clear.
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="#+id/frame_layout_child_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<FrameLayout
android:id="#+id/frame_layout_top"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</merge>
And I want to redirect adding all child view to FrameLayout with id frame_layout_child_container.
So of course I overrode methods addView() like this
#Override
public void addView(View child) {
this.mFrameLayoutChildViewsContainer.addView(child);
}
But for sure this doesn't work because for this time mFrameLayoutChildViewsContainer is not added to the root custom view.
My idea is always keep some view on on the top in this container frame_layout_top and all child views added into custom component should go to frame_layout_child_container
Example of using custom view
<CustomFrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
</CustomFrameLayout>
So in this case TextView should be added to the frame_layout_child_container
Is it possible to delegate adding all views into child ViewGroup like I described.
I have other ideas like using bringToFront() method every time view is added to keep them in correct z-axis order or for example when view is added, save it to array and than after inflating custom view add all views to this child FrameLayout
Suggest what to do in this case in order not to hit performance with reinflating all layout every time new view is added, if it is possible to implement in other way.
Views inflated from a layout - like your example TextView - are not added to their parent ViewGroup with addView(View child), which is why overriding just that method didn't work for you. You want to override addView(View child, int index, ViewGroup.LayoutParams params), which all of the other addView() overloads end up calling.
In that method, check if the child being added is one of your two special FrameLayouts. If it is, let the super class handle the add. Otherwise, add the child to your container FrameLayout.
public class CustomFrameLayout extends FrameLayout {
private final FrameLayout topLayout;
private final FrameLayout containerLayout;
...
public CustomFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.custom, this, true);
topLayout = (FrameLayout) findViewById(R.id.frame_layout_top);
containerLayout = (FrameLayout) findViewById(R.id.frame_layout_child_container);
}
#Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
final int id = child.getId();
if (id == R.id.frame_layout_top || id == R.id.frame_layout_child_container) {
super.addView(child, index, params);
}
else {
containerLayout.addView(child, index, params);
}
}
}
I have a TextView, which I need to be centered horizontally and vertically inside a square. To do this, I have put it inside a RelativeLayout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:background="#fff"
>
<TextView
android:id="#+id/centered_text_view_text"
android:layout_centerInParent="true"
android:text="test"
android:background="#00ff00"
android:textColor="#000"
android:textSize="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</RelativeLayout>
I instantiate the RelativeLayout with a LayoutInflater and then call the layout method on the RelativeLayout to position it where I need it.
LayoutInflater inflater = Helpers.getInflater();
containerLayout = (RelativeLayout) inflater.inflate(R.layout.centered_text_view, null);
textView = (TextView) containerLayout.findViewById(R.id.centered_text_view_text);
... (later in the code)
containerLayout.layout(dayLeft, weekTop, dayRight, weekBottom);
My problem is that the TextView is not visible. I can see the white background for the RelativeLayout, but neither the text nor the background for the TextView.
I have also tried getting rid of the RelativeLayout and calling the layout method on the TextView. I haven't been able to get this to work, however, because the text is only horizontally, and not vertically, aligned after I set the gravity on the TextView.
First - Custom views should extend some sort of View.class. The class to extend should be whatever your root view is in your xml.
Second - You are keeping a reference to containerLayout (root view) which is redundant. This class is the root view.
Replace containerLayout with this or just removing entirely:
textView = (TextView) findViewById(R.id.centered_text_view_text);
Third - I can't say how you are inflating is wrong, but I have never done it like that.
Instead of calling layout on a containerLayout, try inflating with the inflate() method. (You get access to this when extending a sub class of View)
inflate(getContext(), R.layout.view_user_tag, this);
Notice how I am setting this as the root, and using the inflater from the view class.
Your class may look like something like this:
public static class TextInLinear extends RelativeLayout {
public TextView textView;
//region default constructers
public void TextInLinear(Context context){
this(context, null);
}
public void TextInLinear(Context context, AttributeSet attrs){
this(context, attrs, 0);
}
public void TextInLinear(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if(!isInEditMode()) init();
}
//endregion
private void init(){
inflate(getContext(), R.layout.view_user_tag, this);
textView = (TextView) this.findViewById(R.id.centered_text_view_text);
}
/* more methods. i.e. setText(), getText() or whatever */
}
I'm having trouble creating a custom view.
If my understanding is correct, i wish for a compound view where ui elements like Switch can be incorporated within a custom view. An example of intention of this will be a modal view that displays a series of switches with a save & back button with relative actions, in context to an in-app form purpose.
In other examples/questions people have mentioned this.addView(View childView) however when i try to use this function i get an unresolved symbol error from android studio. When executed, the app displays blank.
public class NewView extends View {
private Switch switchOne;
private Switch switchTwo;
public NewView(Context context){
super(context);
init(null, 0);
}
public NewView(Context context, AttributeSet attrs){
super(context);
init(attrs, 0);
}
public NewView(Context context, AttributeSet attrs, int defStyle){
super(context);
init(attrs, defStyle);
}
private void init(AttributeSet attrs, int defStyle) {
//LayoutInflater.from(getContext()).inflate(R.layout.customview, (ViewGroup) this.getParent(), true); //First attempted this approach
((Activity) getContext()).getLayoutInflater().inflate(R.layout.customview, (ViewGroup) this.getParent()); //then attempted this approach
this.switchOne = (Switch) findViewById(R.id.firstSwitch);
this.switchTwo = (Switch) findViewById(R.id.secondSwitch);
}
#Override
public void onDraw(Canvas canvas){
super.onDraw(canvas);
}
}
customview.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/mainView">
<Switch
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/firstSwitch"/>
<Switch
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/secondSwitch"/>
</LinearLayout>
activity_main.xml
<LinearLayout 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" android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:orientation="vertical"
android:paddingBottom="#dimen/activity_vertical_margin" tools:context=".MainActivity">
<com.example.simonaddicott.customviewone.NewView
android:layout_height="match_parent"
android:layout_width="match_parent"
android:id="#+id/myNewView"
/>
</LinearLayout>
Extend from ViewGroup, override onLayout and provide an implementation. Alternatively, since you seem to just want the switches laid out vertically, just extend from LinearLayout to reuse its layout rules and set the orientation for your NewView in the activity_main.xml, or in code if you want to override the xml setting and enforce the vertical layout.
Replace the root LinearLayout in customview.xml with a merge.
When inflating, use this and not this.getParent() as the second parameter
I have a scrollview containing a textview in an Android app. This textview will have text appended to it continuously at set intervals. Scrolling works and the text adds just fine, but what I'd like to do is have the scrollview autoscroll down as text is added. As new text is appended at the bottom, it automatically scrolls down to match and old text is pushed out of sight at the top. What would be even better is to add text to the bottom of the scrollview and have it push older text upward, but one thing at a time.
<ScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:fillViewport="true"
>
<TextView
android:id="#+id/statusText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />
</ScrollView>
I've tried the solution with gravity but result was a lot of empty space under actual text when scroll to bottom.
So that is another way. You can place your TextView inside of ScrollView:
<ScrollView
android:id="#+id/scrollView1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fillViewport="true" >
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
And define addTextChangedListener
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
//some code here
ScrollView scrollView1 = (ScrollView) findViewById(R.id.scrollView1);
TextView textView1 = (TextView) findViewById(R.id.textView1);
textView1.addTextChangedListener(new TextWatcher() {
#Override
public void afterTextChanged(Editable arg0) {
scrollView1.fullScroll(ScrollView.FOCUS_DOWN);
// you can add a toast or whatever you want here
}
#Override
public void beforeTextChanged(CharSequence arg0, int arg1,
int arg2, int arg3) {
//override stub
}
#Override
public void onTextChanged(CharSequence arg0, int arg1, int arg2,
int arg3) {
//override stub
}
}) }
Actually i noticed right now that the application will scroll every time the text is changed, not just added. However it works just fine for me.
You can simply scroll the whole ScrollView to the bottom when text is added.
textView.append(text);
scrollView.fullScroll(View.FOCUS_DOWN);
As #RaphaelRoyer-Rivard suggested in his comment, you can get more solid result with post:
textView.append(text);
scrollView.post(() -> scrollView.fullScroll(View.FOCUS_DOWN));
If you're looking for a more generic way of auto-scrolling to the bottom of a ScrollView, you could try the snippet below. It has the advantage that you don't need to post a Runnable to make sure you're in the UI thread. It has the disadvantage that I'm not sure what things this could break.
public class AutoScroller extends ScrollView {
public boolean autoscroll = true;
public AutoScroller(Context context) {super(context);}
public AutoScroller(Context context, AttributeSet attrs) {super(context,attrs);}
public AutoScroller(Context context, AttributeSet attrs, int defStyleAttr){super(context, attrs, defStyleAttr);}
public AutoScroller(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(autoscroll) {
scrollTo(getScrollX(), getChildAt(getChildCount()-1).getBottom() - getHeight());
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
Another candidat to override might be onLayout.
textView.append(text);
scroll.fullScroll(View.FOCUS_DOWN)
this will give problem, it won't scroll upto extreme top, due to android:layout_gravity="bottom"