Android: Inherit from TabHost, e.g. using TabHost without Activity - android

I wanted to use a TabHost inside an existing TabHost/TabActivity. So I wanted to get rid of the Activity class and just implement a viewgroup class inheriting from TabHost. However, it seems like it is not possible to use TabHost without a corresponding Activity class - the TabHost and it's contents just won't show up when displaying the extended class.
I used the TabHost class and added the linear layout containing the tabs and content containers with the appropriate IDs (android:id/tabs and android:id/tabcontent) from xml.
public class EmitterView : TabHost, TabHost.ITabContentFactory
{
public EmitterView(Context context) :
base(context)
{
Initialize();
}
public EmitterView(Context context, IAttributeSet attrs) :
base(context, attrs)
{
Initialize();
}
private void Initialize()
{
// construct the TAB Host
LayoutInflater inflater = (LayoutInflater)
this.Context.GetSystemService(Context.LayoutInflaterService);
View view = inflater.Inflate(AndroidFilterEditor.Resource.Layout.EmitterTabHost, null);
this.AddView(view);
this.Setup();
// Create Tabs
TabSpec emitter = this.NewTabSpec("Emitter");
emitter.SetIndicator("Emitter", Resources.GetDrawable(AndroidFilterEditor.Resource.Drawable.ic_tab_artists));
//Intent emitterIntent = new Intent(this.Context, typeof(EmitterActivity));
emitter.SetContent(this);
this.AddTab(emitter);
this.Invalidate();
}
public View CreateTabContent(string tag)
{
EmitterContentView view = new EmitterContentView(this.Context);
return view;
}
}
The Layout definition:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="5dp">
<FrameLayout
android:id="#android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="5dp"/>
<TabWidget
android:id="#android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Any idea if this could be possible? This is Mono for Android btw, so the code is in C# obviously.

You can use ActivityGroup to host more than one activity in it and assign tabhost to each of them. Though it gets complicated and is not encouraged. I will suggest you to use Fragments instead of tabhost inside another tabhost.

Related

Android - create library or core app for similiar apps

I have several apps whose behavior is the same, but only a few different sub menus. the problem is, when there should be a change, i have to modify all the apps. I want to create a core library of these applications. My idea is, I create a library, in the library I create a class to handle the main layout. then on apps calling the class from the library.
class from the library I will create :
public class LayoutActivity extends LinearLayout {
private View mLayout;
private Button button;
private TabLayout tabLayout;
public LayoutActivity(Context context) {
super(context);
init(null);
}
public LayoutActivity(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
private void init(AttributeSet attrs) {
mLayout = LayoutInflater.from(getContext()).inflate(R.layout.activity_layout, this, true);
button = (Button) findViewById(R.id.btn);
tabLayout = (TabLayout) findViewById(R.id.tablayout);
}
public View getLayout() {
return mLayout;
}
}
<?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">
<Button
android:id="#+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<android.support.design.widget.TabLayout
android:id="#+id/tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
class from my app :
public class MainActivity extends AppCompatActivity {
LayoutActivity layout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
layout = new LayoutActivity(getApplicationContext());
setContentView(layout.getLayout());
}
however when I enter TabLayout on the layout of my library, I get an error like this:
when I didn't enter a component like Tab Layout or Toolbar I did not get any error.
Does anyone know this error why? or is there another idea to create libraries from my apps?

Databinding returning null. Is it normal?

I'm using databinding, and looks like tab item is returned null from databinding (but not gameTypes), is that normal? Other views are working fine, so there is no problem with implementation of databinding. Here is part of layout file.
<android.support.design.widget.TabLayout
android:id="#+id/gameTypes"
android:layout_width="0dp"
android:layout_height="48dp"
android:background="#android:color/white"
>
<android.support.design.widget.TabItem
android:id="#+id/football"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="football"
android:text="Football"
/>
<android.support.design.widget.TabItem
android:id="#+id/basketball"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="basketball"
android:text="Basketball"/>
</android.support.design.widget.TabLayout>
Here is code that I'm trying to find selected tab;
private Boolean isSelectedTab(TabItem item, TabLayout.Tab tab) {
if (tab.getTag().equals(item.getTag()))
return true;
return false;
}
Error is; item is null which is databinding.basketball. I think shouldn't be null since it's TabItem.
Thanks.
Your code does not line up with your XML, so that's probably one problem. But you might be running into an issue we had with TabLayouts and databinding.
The root cause appears to be that TabLayout uses TabItem in XML, but it converts it into a Tab at runtime. This screws up the internal mapping databinding uses to create its references. In our case, our TabItem instance was being cast from the wrong object. In your case, sounds like it might just be a null object.
In either case, it does't appear that you can use TabLayout with databinding reliably. We ended up creating a custom View that just wraps a TabLayout and then "binds" data to it manually by accessing its Tab items directly.
For example, a layout with a TabLayout:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="viewModel" type="com.app.ViewModel" />
</data>
<android.support.design.widget.TabLayout
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="#color/white"
app:onTabSelectedListener="#{viewModel.onTabSelectedListener}">
<android.support.design.widget.TabItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="#layout/custom_tab_layout">
</android.support.design.widget.TabItem>
<android.support.design.widget.TabItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="#layout/custom_tab_layout">
</android.support.design.widget.TabItem>
</android.support.design.widget.TabLayout>
And then a custom view that wraps that:
public class CustomTabLayout extends FrameLayout {
private CustomTabLayoutBinding mBinding;
public CustomTabLayout(#NonNull Context context) {
this(context, null);
}
public CustomTabLayout(#NonNull Context context, #Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomTabLayout(#NonNull Context context, #Nullable AttributeSet attrs, #AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater inflater = LayoutInflater.from(context);
View tabLayout = inflater.inflate(R.layout.custom_tab_layout, this, false);
addView(tabLayout);
if (!isInEditMode()) {
mBinding = CustomTabLayoutBinding.bind(tabLayout);
}
}
public void setViewModel(#Nullable ViewModel viewModel) {
mBinding.setViewModel(viewModel);
if (viewModel != null) {
updateTabAtIndex(viewModel.getFirstTabViewModel(), 0, viewModel.getSelectedIndex());
updateTabAtIndex(viewModel.getSecondTabViewModel(), 1, viewModel.getSelectedIndex());
}
}
private void updateTabAtIndex(TabViewModel tabViewModel, int index, int selectedIndex) {
TabLayout.Tab tab = mBinding.tabLayout.getTabAt(index);
if (tab == null) {
return;
}
View customView = tab.getCustomView();
if (customView == null) {
return;
}
if (index == selectedIndex) {
tab.select();
}
TextView textView = (TextView) customView.findViewById(R.id.title);
textView.setText(tabViewModel.getTitleText());
TextView subTitleTV = (TextView) customView.findViewById(R.id.subtitle);
subTitleTV.setText(tabViewModel.getSubTitleText());
}
}
Then in the layout that you need a TabLayout, use your custom view instead:
<com.app.CustomTabLayout
android:id="#+id/custom_tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="#dimen/default_toolbar_elevation"
app:viewModel="#{viewModel.getTabsViewModel}" />
We're using MVVM here, but hopefully you get the gist: by providing a public setViewModel (or setMyData or whatever) method, you can still leverage databinding where you use the custom tab layout, but then control manually setting the attributes on the Tab objects in the TabLayout.
Hope that helps!
item is databinding.itemBasketball
Not according to your code snippet. Your code snippet shows that you are calling isSelectedTab(b.itemFootballTypes, tab). There is nothing named itemFootballTypes in your layout XML in your question. There is an itemFootball tab, though.

Alternatives to creating a Compound Control by extending a Layout

I want to create a custom Compound Control in Android that holds some logic. For the purpose of this example, let's say I want it to switch between two views when clicked.
According to the API guide, it looks like the way to do that is to create a new class that extends Layout, and do everything in there.
So I did just that:
I created a XML layout to inflate for my custom component:
.
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/view1"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="Hello"/>
<TextView
android:id="#+id/view2"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="World"
android:visibility="gone"/>
</RelativeLayout>
Then I created my custom Layout class, and added the logic in there:
public class MyWidget extends RelativeLayout {
public final View mView1;
public final View mView2;
public MyWidget(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
RelativeLayout view = (RelativeLayout) inflater.inflate(R.layout.my_widget, this, true);
mView1 = view.findViewById(R.id.view1);
mView2 = view.findViewById(R.id.view2);
view.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
switchViews();
}
});
}
public void switchViews() {
if (mView1.getVisibility() == View.VISIBLE) {
mView1.setVisibility(View.GONE);
} else {
mView1.setVisibility(View.VISIBLE);
}
if (mView2.getVisibility() == View.VISIBLE) {
mView2.setVisibility(View.GONE);
} else {
mView2.setVisibility(View.VISIBLE);
}
}
}
And finally, I included my custom view in some layout:
.
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.example.MyWidget
android:layout_height="match_parent"
android:layout_width="match_parent"/>
</RelativeLayout
And that works.
I am not completely happy with that solution though, for 2 reasons:
In the constructor of MyWidget, I instantiate 2 nested RelativeLayout by calling the super() constructor, and the one that is at the root of my XML layout. For that, I know I can instead use <merge> as my XML root and that gets me rid of the extra RelativeLayout. Except that defining XML attributes, such as android:background on my <merge> tag doesn't have any effect, so I have to define it programmatically, which is not as nice.
The custom View is a subclass of RelativeLayout, and therefore expose methods it inherits from it, such as addView(), even if it doesn't make sense to add child views to it. I know I can override those methods to prevent users from doing that, but I would still find it cleaner to inherit from View.

addview to layout

I have create class that extend View, i`m trying to layout.addView(somthing) in layout, not exception is trowed.
layout
<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="wrap_content" >
</RelativeLayout>
class
public class Navigator extends View {
Context context;
RelativeLayout layout = (RelativeLayout) findViewById(R.layout.navigator);
public Navigator(Context context) {
super(context);
this.context = context;
}
TextView tab1 = new TextView(context);
tab1.setText("blah");
PROBLEM-> layout.addView(tab1);
}
Are you adding your extended view to an Activity?
Because you are modifying your view but I can't see where are you attaching your "Navigator" view.
Another thing is that your RelativeLayout declared on the XML has no ID, so you will never find a RelativeLayout with R.id.navigator id.

Dynamically adding views to mixed xml/code compound layout

Sorry if this redundant with the ton of questions/answers on inflate, but I could not get a solution to my problem.
I have a compound view (LinearLayout) that has a fixed part defined in XML and additional functionalities in code. I want to dynamically add views to it.
Here is the XML part (compound.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/compoundView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView android:id="#+id/myTextView"
android:layout_width="110dp"
android:layout_height="wrap_content"
android:text="000" />
</LinearLayout>
I have defined in code a LinearLayout to refer to the XML:
public class CompoundControlClass extends LinearLayout {
public CompoundControlClass (Context context) {
super(context);
LayoutInflater li;
li = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
li.inflate(R.layout.compound_xml,*ROOT*, *ATTACH*);
}
public void addAView(){
Button dynBut = new Button();
// buttoin def+layout info stripped for brevity
addView(dynBut);
}
}
I tried to programmatically add a view with addAView.
If ROOT is null and ATTACH is false, I have the following hierarchy (per HierarchyViewer):
CompoundControlClass>dynBut
The original TextView in the XML is gone.
If ROOT is this and ATTACH is true, I have the following hierarchy:
CompoundControlClass>compoundView>myTextView
CompoundControlClass>dynBut
I would like to have
CompoundControlClass>myTextView
CompoundControlClass>dynBut
where basically the code and XML are only one unique View.
What have I grossly missed?
ANSWER BASED on feedback from D Yao ----------------------
The trick is to INCLUDE the compound component in the main layout instead of referencing it directly.
activity_main.xml
<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">
<include layout="#layout/comound"
android:id="#+id/compoundView"
android:layout_alignParentBottom="true"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
mainActivity.java
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CompoundControlClass c = (CompoundControlClass) this.findViewById(R.id.compoundView);
c.addAView(this);
}
}
CompoundControlClass.java
public class CompoundControlClass extends LinearLayout {
public CompoundControlClass(Context context) {
super(context);
}
public CompoundControlClass(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CompoundControlClass(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void addAView(Context context){
ImageView iv = new ImageView(context);
iv.setImageResource(R.drawable.airhorn);
addView(iv);
}
}
compound.xml
<com.sounddisplaymodule.CompoundControlClass xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/compoundView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:layout_width="110dp"
android:layout_height="wrap_content"
android:gravity="right"
android:textSize="40sp"
android:textStyle="bold"
android:text="0:00" />
</com.sounddisplaymodule.CompoundControlClass>
Why not just call addView on the linearlayout? I don't see the need for CompoundControlClass based on the needs you have listed.
LinearLayout v = (LinearLayout)findViewById(R.id.compoundView);
v.addView(dynBut);
In this case, v will contain myTextView, then dynBut.
if you wish to have other functions added and thus really feel a need for creating the compound control class, just leave the constructor as super(etc) and remove the rest
Then your xml would look like this:
<com.yourpackage.CompoundControlClass xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/compoundView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView android:id="#+id/myTextView"
android:layout_width="110dp"
android:layout_height="wrap_content"
android:text="000" />
</com.yourpackage.CompoundControlClass>
you will also have to ensure your CompoundControlClass.java contains the appropriate Constructor which takes both a Context and an attribute set.
Then, in your java, after you've called setContentView, you can do the following:
CompoundControlClass c = (CompoundControlClass)findViewById(R.id.compoundView);
Button b = new Button(context);
//setup b here or inflate your button with inflater
c.addView(b);
this would give you your desired heirarchy.

Categories

Resources