Programmatic approach to ViewSwitcher.ViewFactory makeView() method - java

I've been trying to create a simple class that implements the ViewSwitcher.ViewFactory interface basing on the project developed in "Sams Teach Yourself Android Application Development in 24 Hours". In the example the makeView() method inflates a layout in order to obtain a View. However, I wanted to do it programmatically and it didn't work.
The onCreate() method of the activity looks like this:
private TextSwitcher mQuestionText;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.game);
mQuestionText = (TextSwitcher) findViewById(R.id.MyTextSwitcher);
mQuestionText.setFactory(new MyTextSwitcherFactory());
mQuestionText.setCurrentText("blablabla");
}
The proposed solution goes like this:
private class MyTextSwitcherFactory implements ViewSwitcher.ViewFactory {
public View makeView() {
TextView textView = (TextView) LayoutInflater.from(
getApplicationContext()).inflate(
R.layout.text_switcher_view,
mQuestionText, false);
return textView;
}
}
The resource file is:
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:textColor="#color/title_color"
android:textSize="#dimen/game_question_size"
android:gravity="center"
android:text="Testing String"
android:layout_height="match_parent"
android:padding="10dp">
</TextView>
While I wanted to it like this:
private class MyImageSwitcherFactory implements ViewSwitcher.ViewFactory {
public View makeView() {
TextView textView = new TextView(getApplicationContext());
textView.setLayoutParams(new TextSwitcher.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
textView.setTextColor(R.color.title_color);
textView.setTextSize(R.dimen.game_question_size);
textView.setGravity(Gravity.CENTER);
textView.setText("Test");
return textView;
}
}
I get the "blablabla" text displayed when I use the inflate method but not doing it programmatically. Could you point to an error in my code, please?

mQuestionText = (TextSwitcher) findViewById(R.id.MyTextSwitcher);
You're finding the view by the id MyTextSwitcher. Make sure you set that id if you're creating the view programmatically.
view.setId(R.id.MyTextSwitcher);
Update:
Whoops, didn't read your code carefully enough. You're right, since you're inflating from the XML, your ID should be set properly already. What you probably are missing is actually adding the view to the view hierarchy. You need to find a parent ViewGroup (e.g. a LinearLayout or a 'RelativeLayout, etc.) under which you want to putMyTextSwitcher, let's call itroot` and add it with something like:
root.addView(view);

I found out what was wrong with the code I provided. This is wrong since it gives the ID of the resource as the function parameter instead of the value itself.
textView.setTextColor(R.color.title_color);
textView.setTextSize(R.dimen.game_question_size);
This is correct:
textView.setTextColor(getApplicationContext().getResources().getColor(R.color.title_color));
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getApplicationContext().getResources().getDimension(R.dimen.game_question_size));

Related

How to connect a ListView that's not in the main layout?

I am working on my first rather big project in android studio and I've been struggling for the past three days with this:
My app has a bottom navigation component that opens 3 other fragments and I was wondering how I could implement a ListView into one of those 3 fragments. I managed to put the ListView in the main layout (activity_main.xml), but as soon as I put the ListView into a layout that's not the main layout I get an NPE. I guess that's because if I try to link the ListView to its data with findViewById(R.id.ListView) it's looking for ListView in activity_main. The code that tries to link the ListView to the component in the layout-file is written in the onCreate in the mainActivity.java. How can I tell the programm to look for the ListView in layout_With_List.xml?
I read on another question that I need to change the setContentView(R.layout.activity_main) in onCreate to setContentView(R.layout.layout_With_List), but that just creates another error.
So how can I properly put a ListView into another fragment, so it won't float around in the main layout when I change tabs with the bottom navigation AND make it display data?
The error i'm getting:
Attempt to invoke virtual method 'void android.widget.ListView.setAdapter(android.widget.ListAdapter)' on a null object reference
This is the Relevant code bits:
MainActivity.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv_productlist = findViewById(R.id.lv_productlist);
showFoodOnListView(db_helper);
}
The code that passes data to the ListView:
private void showFoodOnListView(DB_Helper db_helper2) {
food_ArrayAdapter = new ArrayAdapter<Product_Model>(MainActivity.this, android.R.layout.simple_list_item_1, db_helper2.getEveryone());
lv_productlist.setAdapter(food_ArrayAdapter);
}
layout_With_List.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/lv_productlist"
android:name="com.ocdm.prepper.ListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context=".ListFragment"
tools:listitem="#layout/fragment_list" />
===========================SOLUTION===============================
Thanks to [https://stackoverflow.com/users/4039784/hayssam-soussi]
I could figure it out.
lv_productlist = (ListView) view.findViewById(R.id.R.id.lv_productlist);
showFoodOnListView(db_helper);
I had to move the code above into the Layouts corresponding java class, e.g fragment_A.java.
Then I had to move the method showFoodOnListView into fragment_A.java
and set the context to getActivity():.
food_ArrayAdapter = new ArrayAdapter<Product_Model>(getActivity(), android.R.layout.simple_list_item_1, db_helper2.getEveryone());
Let's say you need to add the ListView to Fragment A
First you have to create a layout for Fragment A (i.e fragment_a.xml)
Add your ListView to fragment_a.xml
Then your FragmentA.java code should look like this:
public class FragmentA extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_a, container, false);
// get the reference of ListView
lv_productlist = (ListView) view.findViewById(R.id.lv_productlist);
showFoodOnListView(db_helper);
return view;
}
}

In Android, how to pass data from class to corresponding layout/fragment file?

Background: I am writing an Android app, mostly following instructions from the official developer guides. I have some experience with writing Java code but little with xml and Android.
Question: I would like to pass information from variables in my static class "PlaceholderFragment" (which is contained by my "BoardContainer" class) to my fragment layout file "fragment_board.xml". PlaceholderFragment looks like this (mostly unedited after Eclipse created it for me):
public static class PlaceholderFragment extends Fragment {
public int nButtons = 2;
public static class PlaceholderFragment extends Fragment {
private int nButtons = 2;
public PlaceholderFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_board,
container, false);
return rootView;
}
}
(other lifecycle callbacks have not yet been implemented).
Now my fragment_board.xml is like this:
<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"
tools:context="com.example.exampletest.MainGame$PlaceholderFragment" >
<ImageButton
android:id="#+id/imageButton1"
android:layout_width="49dp"
android:layout_height="49dp"
android:contentDescription="#null"
android:onClick="buttonPressed" //not yet implemented
android:src="#drawable/grid2" />
</RelativeLayout>
Here I would like to use the int instance variable nButtons so that, for example, if nButtons==7, then we get android:src="#drawable/grid7 instead of grid2, or that the layout file will contain seven ImageButtons instead of just one, and so forth. In other words, how do I make the xml file read and understand the instance variables from its corresponding class?
Unfortunately XML files can not read and understand the variables from its corresponding class. Instead we can alter them programatically by obtaining a handle to components contained within XML files in a class file and altering them like this:
ImageButton imgHandle = (ImageButton) findViewById(R.id.imageButton1);
if(nButtons == 7) {
imgHandle.setImageResource(R.id.grid7);
}
In a fragment you're going to need to use rootView inside of your onCreateView method:
ImageButton imgHandle = (ImageButton)rootView.findViewById(R.id.imageButton1);

Have button action in fragment instead of main

I have an Android app where i'm using tabs (with ActionBarSherlock). So my main activity creates the tabs for me and from there i load in the fragment layouts.
In my MainActivity.java i create a tab (this is just a snippet):
mTabsAdapter = new TabsAdapter(this, mViewPager);
mTabsAdapter.addTab(
bar.newTab().setText("Fragment 1"),
MainMenuFragment.class, null);
My MainMenu.java looks like this:
public class MainMenuFragment extends SherlockFragment
{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.mainmenu_fragment, container, false);
return view;
}
public void showMainMenu(View view)
{
Log.e("app", "olol: button!"); // never called!!
}
}
And this is mainmenu_fragment
<?xml version="1.0" encoding="utf-8"?>
<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"
android:background="#000000" >
<Button
android:id="#+id/btnMenu"
android:layout_width="170dp"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="41dp"
android:text="#string/mainmenu"
android:onClick="showMainMenu" />
</RelativeLayout>
Now all i have to do is place the method showMainMenu(View view) somewhere. I thought this would go in the corresponding java file (MainMenuFragment.java in my case). But it only works when i put the method in the MainAvtivity.java file.
That means that all my button actions from all kinds of fragment layouts will go in one (the main) java file. Why can't i simply place it inside the java file that calls the Fragment layout..??
The short answers is (like already pointed out), you can't.
The only way to do this is by creating an onClick even listener. In the MainMenuFragment.java in the onCreate method, do something like this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.scan_fragment, container, false);
Button menuButton = (Button)view.findViewById(R.id.btnMenu);
menuButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Log.e("app", "onclick listener");
}
});
return view;
}
You can remove the onClick attribute from the layout xml.
Now all I have to do is place the method showMainMenu() somewhere - this is wrong. Please, refer to the documentation of android:onClick :
For instance, if you specify android:onClick="sayHello", you must declare a public void sayHello(View v) method of your context (typically, your Activity).
Seems You cannot place your callback somewhere, because framework won't be able to find that callback. If You're defining it inside Activity (which is actually, a Context), it's possible for View to find it back. Actually, View.java contains the following code:
case R.styleable.View_onClick:
if (context.isRestricted()) {
throw new IllegalStateException("The android:onClick attribute cannot " + "be used within a restricted context");
}
final String handlerName = a.getString(attr);
mHandler = getContext().getClass().getMethod(handlerName, View.class);
...
mHandler.invoke(getContext(), View.this);
Seems it's the only possible way to call callback defined in layout file with current android:onClick attribute specification.
I solved this using the following:
Fragment xml contains
android:onClick="myFunction"
Activity Contains
public void myFunction(View v)
{ getSupportFragmentManager().findViewById/Tag(...).myFunction(v); }
Fragment Code can then implement as below to have access to local data
public void myFunction(View v) {...}
Hope this helps.

How to transfer data between Views in a Layout

I haven't beeen able to find the answer to the following question:
How do I get data from View A to View B, with View A and View B in the same LinearLayout? Is this even possible? Do I need to start working with threads?
I haven't been able to get the correct search phrase I guess, I'm probably not the first person that wants to do this, but I can't find it :(
Below is what I use now to create the views. In the TargetTrainer (which extends View) I'm letting the user give some input, and I'd like to be able to give feedback to the user in the TextView. How would I for instance show the coordinates of the onTouchEvent of TargetTrainer in the TextView?
Below is a clipped/simplified version of my program.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
LinearLayout linear;
linear = new LinearLayout(this);
linear.setOrientation(LinearLayout.VERTICAL);
TextView text = new TextView(this);
text.setText("Test");
linear.addView(text);
Display display = getWindowManager().getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
TargetTrainer t = new TargetTrainer(this, width, height);
linear.addView(t);
setContentView(linear);
}
As I can see from the snippet, you already pass Context in the constructor new TargetTrainer(this, width, height). Assuming that the code you provided is from activity called BaseActivity create reference to BaseActivity in the TargetTrainer constructor and call the update method from TargetTrainer.
public TargetTrainer extends View {
....
BaseActivity mBaseActivity = null;
public MyView(Context context, int width, int height) {
....
mBaseActivity = (BaseACtivity)context;
....
}
....
private void update(String text)
{
mBaseActivity.updateTextView(text);
}
}
In BaseActivity create updateTextView:
public void updateTextView(String updateText){
text.setText(updateText);
}
You should set the id of that TextView, listen to the touch events in your TargetTrainer, and when one occures, you use
final TextView tv = (TextView)TargetTrainer.this.findViewById(R.id.myTextView);
tv.setText(touchEvent.toString());
That's it.
Update
It would be much cleaner, if you'd build your main layout from an xml source.
You need to create a new layout xml inside the /res/layout that looks like the one you're creating inside your onCreate method:
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:id="#+id/myTextView" android:text="Test"
android:layout_width="fill_parent" android:layout_height="wrap_content" />
<!-- change the your.package part to match the package declaration
of your TargetTrainer class -->
<your.package.TargetTrainer android:id="#+id/myTargetTrainer"
android:layout_width="fill_parent" android:layout_height="fill_parent" />
</LinearLayout>
This way a new entry will be placed inside your R class' static layout class with the name main.
You can reference it by R.layout.main.
Note, that in this xml you have id attributes defined for both
your TextView: myTextView, and
your TargetTrainer:
'myTargetTrainer'.
The #+id inside the xml tag means that you are creating a new id with the name after the '/' sign.
This also will create new members inside your R class' static id class with the names you've provided: myTextView and myTargetTrainer that are accessible from now on from anywhere in your code.
If you've built this xml, your onCreate method will look like this:
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// attach the OnTouchListener to your TargetTrainer view:
(TargetTrainer)findViewById(R.id.myTargetTrainer).setOnTouchListener(this);
}
You also have to extend your main activity class to implement the View.OnTouchListener interface, and add the necessary method at the end of your class:
#Override
public boolean onTouch(View view, MotionEvent event)
{
//note, that here the view parameter is the view the touch event has been dispatched to
final TextView tv = (TextView)findViewById(R.id.myTextView);
tv.setText(event.toString());
return true; //or false, if you are dealing further with this event in parent classes
}

Clickable ListView

I'm looking now a few days for a solution for clickable items in a listView.
First I came across this:
developer.android.com/resources/articles/touch-mode.html
and found that it's doesn't have the "normal" onListItemClick() behavouir.
Then I came across of this code:
http://www.androidsnippets.org/snippets/125/
// LINE 296-321
#Override
protected ViewHolder createHolder(View v) {
// createHolder will be called only as long, as the ListView is not filled
// entirely. That is, where we gain our performance:
// We use the relatively costly findViewById() methods and
// bind the view's reference to the holder objects.
TextView text = (TextView) v.findViewById(R.id.listitem_text);
ImageView icon = (ImageView) v.findViewById(R.id.listitem_icon);
ViewHolder mvh = new MyViewHolder(text, icon);
// Additionally, we make some icons clickable
// Mind, that item becomes clickable, when adding a click listener (see API)
// so, it is not necessary to use the android:clickable attribute in XML
icon.setOnClickListener(new ClickableListAdapter.OnClickListener(mvh) {
public void onClick(View v, ViewHolder viewHolder) {
// we toggle the enabled state and also switch the icon
MyViewHolder mvh = (MyViewHolder) viewHolder;
MyData mo = (MyData) mvh.data;
mo.enable = !mo.enable; // toggle
ImageView icon = (ImageView) v;
icon.setImageBitmap(
mo.enable ? ClickableListItemActivity.this.mIconEnabled
: ClickableListItemActivity.this.mIconDisabled);
}
});
While debugging I noticed the parameter View v is a TextView and not a "normal" View and then of course:
TextView text = (TextView) v.findViewById(R.id.listitem_text);
returnes null and I get a NullPointerException...
Any ideas why? And how I can solve this?
Thanks in advance! :)
How do you create your instance of ClickableListAdapter ?
When you create your list adapter, you have to pass a resource id viewId, this should be a layout which will be inflated later.
public ClickableListAdapter(Context context, int viewid, List objects) {
// Cache the LayoutInflate to avoid asking for a new one each time.
mInflater = LayoutInflater.from(context);
mDataObjects = objects;
mViewId = viewid;
Below, the code inflate the xml layout passed to the constructor and call createHolder.
view = mInflater.inflate(mViewId, null);
// call the user's implementation
holder = createHolder(view);
So make sure that when instantiating your ClickableListAdapter, you pass a layout instead of an id
Edit
You have to create a xml layout with the following which is taken from the link you have provided:
<?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="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
>
<TextView android:text="Text" android:id="#+id/listitem_text"
android:layout_weight="1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
></TextView>
<ImageView android:id="#+id/listitem_icon"
android:src="#drawable/globe2_32x32"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="32px"
android:maxHeight="32px"
>
</ImageView>
</LinearLayout>
If you call it mylistrow.xml in the layout directory, so you construct your adapter as :
adapter = new MyClickableChannelListAdapter(this, R.layout.mylistrow, channelList);
setListAdapter(adapter);
List items should be clickable right out of the box. You can check how lists are coded by looking at ApiDemos project code. It should be present on your local machine since it is a part of the SDK. I have it at <root_sdk_folder>\platforms\android-2.0.1\samples\ApiDemos.

Categories