I already created this with custom adapter in another project, but I didn't use fragments. I now have a project using fragments, and am displaying the listview in a fragment. I don't know or am able to find exactly what rules and what classes/java files I need for this to work in a fragment.
Every example on the internet I've used develops an error in some way, and since I don't understand every aspect of how this is done I can't fix it on my own.
In my previous project, I did this (CalculationsActivity.java):
public class CalculationsActivity extends AppCompatActivity implements Serializable {
//content of my class
}
class CustomAdapter extends BaseAdapter {
#Override
public int getCount() {
return arrayLi.size();
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
view = getLayoutInflater().inflate(R.layout.customlayout, null);
ImageView imageView = (ImageView) view.findViewById(R.id.imageView);
TextView content = (TextView) view.findViewById(R.id.content);
TextView date = (TextView) view.findViewById(R.id.date);
imageView.setImageResource(R.drawable.calcer);
String[] convertedArrLi = arrayLi.toArray(new String[arrayLi.size()]);
String[] convertedDates = dates.toArray(new String[dates.size()]);
content.setText(convertedArrLi[i]);
date.setText(convertedDates[i]);
return view;
}
}
And from that I set an adapter to my listview. This class was in the same java file as the activity that the listview was displayed in. If I do this in my fragment, I get a bunch of red lines. getLayoutInflater() and variables from my other class above it will be red.
As I understand so far you need MainActivity.java, MyFragment.java and Adapter.java. To make your listview work with your array you need to set an adapter including the current activity and the array you want to use. If someone can explain what files I actually need and how they work together (send info to each other and start one another), I would appreciate it.
Note: I have to use a custom adapter.
You can use your custom adapter in activity and in fragment, there is no difference. Can you provide screenshot of your bunch of red lines?
Also your question is incorrect: "what files do I need?". You need classes, first class for activity or fragment to create/declare/initialize second class (custom adapter) and fill it with data.
Related
I'm new to android, but have a good JavaFX experience. I'm trying to create a custom view that i can reuse, but having a hard time figuring out the correct way to do it.
In javafx i could achieve this by: Creating a separate fxml file defining the layout of the custom view, then create a controller class linked to the fxml file, in that class, i'd have a method to retrieve the data model of the controller and use it to fill in the labels, etc.
The custom view i want would be
Constrained Layout
TextView (constrained to right anchor)
Round TextView (constrained to left anchor)
What is the best way to do this in android? Also, Is it possible to achieve this with a RecyclerView? If yes, how can i use a custom view for each item and set its data?
The question is broad. You may need additional research on creating views
Create a recyclerview in the main.xml,
a separate file with an item view.
You have 3 views in your item view - white background with margins (linearlayout?), right textView, and left textview.
The left textview should have android:background="drawable/round_shape" and round_shape.xml defined in your drawables folder. Everything is done in 3 xml files, main.xml for recyclerview, item.xml, round_background.xml. Then, the recyclerview adapter to bind the textviews with your array, and recyclerview initialization
A typical RV adaptor
public class MyRV extends RecyclerView.Adapter<MyRV.ViewHolder> {
private List<MyModelItemWith2Strings> mDataSet; // You may need to setup an array,
// with 2 String objects - for the right and left textviews
// Use an array of class with 2 elements rather than <String>, e.g. List<MyModelItemWith2Strings>
// pass your model here
// this setData will be used to provide the contents for the textviews
void setData(List< /* set your 2 string class here*/ > dataSet) {
mDataSet = dataSet;
}
static class ViewHolder extends RecyclerView.ViewHolder {
// Here you bind item TV's
// first you declare textviews that you will use to fill with data
// Add any other item views you will need to fill in
public TextView tv;
public TextView tv2;
public ViewHolder(LinearLayout v) {
super(v);
// Bind itemview views here. Put R.id.tv from your itemview.xml
tv = v.findViewById(R.id.....);
tv2 = v...
}
}
// Add your itemview layout here
#Override
public MyRV.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LinearLayout v = (LinearLayout) LayoutInflater.from(parent.getContext())
.inflate(/***R.layout.item_view***/, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
#Override
public void onBindViewHolder( MyRV.ViewHolder h, int position) {
// get content from your model (the above list) and fill in the the itemview textviews
String a= mDataSet.get(position).getItem1();
String b = mDataSet.get(position). getItem2();
...
h.tv.setText(a);
// set clickers if you want to. The clicker class is below.
h.tv.setOnClickListener(new Click(position));
h.tv2.setText(...)
}
// This is obligatory to pass for your RV to initialize. It won't work if you don' t tell Android how to count your array soze
#Override
public int getItemCount() {
return mDataSet.size();
}
// These are my implementation of clickers. I prefer to put them in the nested class of the adapter.
private class Click implements OnClickListener {
private int pos;
Click(int position) {
pos = position;
}
#Override
public void onClick(View p1) {
// get data from your array on click
mDataSet.get(pos);
// Use pos as position on the array, mData.get(pos)
}
}
}
Then, in your main class set a recyclerview
RecyclerView rv = (RecyclerView) findViewById(R.id.rv_In_Main_Xml);
// just additional tunings.
rv.setHasFixedSize(true);
rv.setLayoutManager(new LinearLayoutManager(context)); // <- context = this, if you are in the Main activity
Then set the adapter
MyRV rva = new MyRV();
rva.setData(myArray_with_2_string_objects_to_fill_tvs);
rv.setAdaptor(rva);
And your recycler view gets filled with data
Is it possible to have methods and actual coding inside each item for a GridView?
The app that I am attempting to create is a currency converter, and I am currently displaying 3 images in the gridView: Euros, Pesos, and Rupees.
Once the user clicks on one, I want the open to open up a new XML which displays a textView. The user enters the value of US dollars in the textView and clicks the compute button. The app then displays the converted amount in the bottom of the screen.
The problem is that I am unable to figure out how to open up a new XML every time a picture is clicked on in the gridView. Assuming that I am able to do this, I am also unsure of where to place the code that goes behind the conversions. Would I make a new .java or just place is all in MainActivity.java?
Thanks.
What you might be best doing is when the user clicks on a currency it takes them to another activity where you would then load another xml for whatever you want to show.
In order to detect which item had been clicked you can implement an onItemClickListener for example
gridView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
//this assumes you give the gridview a list of currency which it then displays. Here we get the currency selected to then pass to our new activity
String selectedCurrency = myArrayOfCurrencies.get(position);
//then start new activity and give it the currency. This means we won't have to create an activity for each currency. You just need to create 1 and then based on what currency you give it will change the functionality
Intent intent = new Intent(this, Converter.class);
Intent.putExtra("currency", selectedCurrency);
startActivity(intent);
}
First you should be able to detect the clicks on each item of the GridView by calling the setOnItemClickListener() method.
If you set the clicklistener and you still can't detect the clicks, then most probably you need to add those attribtutes to your imageView in the xml
android:focusable="false"
android:focusableInTouchMode="false"
Second, once you are able to detect the clicks you can start new activity or add fragment that contains that edit text that will promote the user to enter the value.
Third, I would suggest to put the code responsible for the currency conversion in a class separately and create static methods that takes a value and convert it to the other curreny such as:
public class CurrencyConverter {
public static double convertToRupees (String currencyType, double currencyValue){
....
return currencyInRupees;
}
}
and by the way I would suggest you to use RecyclerView with grid layout manager instead of GridView.
I would create more classes.
You asked how to open a different XML file for each gridView item.
Create a custom adapter that extends BaseAdapter.
Override getView and for each view attach the right Xml file, according to the position.
For example:
YourActivity.java:
GridView gridView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
gridView = (GridView) findViewById(R.id.gridView);
gridView.setAdapter(new MyAdapter(getApplicationContext());
}
MyAdapter.java:
...
#Override
public int getCount() {
return XmlArr.length;
}
#Override
public Object getItem(int position) {
return XmlArr[position];
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Xml myXml = (Xml) getItem(position);
Holder holder;
if (convertView == null) {
// Set your view's layout. Consider using LayoutInflater.
// Use a static holder to prevent re-initialization:
holder = new Holder();
// holder.textView = ...
// holder.Xml = ...
// Or whatever you decided to have in each gridView item.
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
holder.Xml = myXml;
...
return convertView;
}
static class Holder() {
TextView tv;
Xml xml;
...
}
I assumed you would used an Xml array (xmlArr).
Now you have option to play with each gridView item as you wish. You can set each view/button/textView an onItemClickListener, or you can also set the whole gridView an onItemClickListener (from YourActivity.java).
Hope this helps.
I want to keep my application thin.
Problem: I would like to reuse my Fragment class code to create 3 different instances in the ViewPager which will have 3 pages. Each Fragment will have a different ImageView or background Drawable. What are best practices regarding this? I noticed that using factory methods like here seem to be good, any other alternatives?
I have one Fragment which has the following methods:
Fragment.java
public static Fragment newInstance(Context context) {
FragmentTutorial f = new FragmentTutorial();
Bundle args = new Bundle();
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment, null);
return root;
}
I have a ViewPagerAdapter class which has the following methods:
ViewPagerAdapter.java
public ViewPagerAdapter(Context context, FragmentManager fm) {
super(fm);
mContext = context;
}
#Override
public Fragment getItem(int position) {
return new FragmentTutorial().newInstance(mContext);
}
#Override
public int getCount() {
return totalPage;
}
What I've found is the "best" way to do it (in my opinion, of course) is to do the following:
Have the fragment contain methods to set the customizable data (background, text, etc)
Note: Be careful of trying to load the data in when first creating the fragment. You may be able to set the data before onCreateView() even runs, or at other times it may run after onCreateView(). I personally use a boolean to check if the data has been set. Inside onCreateView() [or onActivityCreated()], I check if the data has been set already. If it has, load in the data. Alternatively, while setting the data, I check if the views have been created/cached already. This is done by simply having variables to cache the data, say private ImageView mBackgroundView. If the view is not null, then I safely set the data on the views.
The above is also an alternative to using newInstance, although both methods work pretty well. However, for more flexibility, I only use newInstance if a) the data is already known before the fragment has to be inserted and b) the data doesn't need to change according to input from elsewhere much.
Let the ViewPager handle all the data
Pass in all the data - a list of ImageViews, a array of Strings, define where all the data is in Resources, etc - at the very beginning [say, in the constructor]
Have the ViewPager create an ArrayList of the fragments- set up each fragment as early as possible (say when first getting all the data) and add it to the list
Let getCount() just use the size of the list
Let getItem() just get the item in the list at the position
Note: If you have any dynamic data, set it up in the getItem() method. Furthermore, you can always add more data+fragments during runtime as well [just notify the adapter that the dataset has been changed]
Essentially, the fragment is like a simple servant- it does simply the least work necessary. If it doesn't have to handle choosing the data, all the better. It'll thus be far more flexible. Just give methods to set the data/views appropriately on the fragment. Now, the ArrayAdapter can do all the grimy hard work with managing the data and giving it to the appropriate fragment. Take advantage of that.
Now, note that this is assuming you want to use a single layout but want to change different aspects of that layout (texts, background, etc). If you want to make a master fragment class that can use any sort of defined layout, you can but note that it decreases the runtime flexibility (how can you change the text or background to something you get from the internet? You simply can't if you only can define and choose from pre-set layouts).
Either way, the ArrayAdapter should take care of all the different data while the fragment simply does as it's designed to do, in a more flexible manner preferably.
Edit:
Here is the project where I most recently implemented this sort of pattern. Note that it has far more to it, so I'll replace it with some not-so-pseudo pseudo-code in the morning/afternoon.
ViewPager [a bit sloppy with all the different things I was trying to do, including extending from a FragmentStatePagerAdapter without actually using any of the specific features of a StatePagerAdapter. In other words, I still need to work on the lifecycle implementations everywhere]
Fragment [Also may be a bit sloppy but shows the pattern still]
The object (actually another fragment) that uses the ViewPager [it's actually a "VerticalViewpager" from a library, but other than the animations and direction to change the current fragment, it's exactly the same- particularly code-wise]
Edit2:
Here is a more (if overly) simplified example of the pattern described above.
Disclaimer: The following code has absolutely no lifecycle management implementations and is older code that has been untouched since around August '14
Fragment simply allows the user of the fragment to set the background color and the text of the single TextView
Link to BaseFragment
Link to layout file
The adapter creates three instances of the fragment and sets the background color and text of each. Each fragment's text, color, and total fragments is hard coded.
Link to Activity+adapter
Link to layout file
Now, here are the exact relevant portions of the code:
BaseFragment
// Note: Found out later can extend normal Fragments but must use v13 adapter
public class BaseFragment extends android.support.v4.app.Fragment {
FrameLayout mMainLayout; // The parent layout
int mNewColor = 0; // The new bg color, set from activity
String mNewText = ""; // The new text, set from activity
TextView mMainText; // The only textview in this fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the fragment's layout
View view = inflater.inflate(R.layout.fragment_base,container,false);
// Save the textview for further editing
mMainText = (TextView) view.findViewById(R.id.textView);
// Save the framelayout to change background color later
mMainLayout = (FrameLayout) view.findViewById(R.id.mainLayout);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// If there is new text or color assigned, set em
if(mNewText != ""){
mMainText.setText(mNewText);
}
if(mNewColor != 0){
mMainLayout.setBackgroundColor(mNewColor);
}
}
#Override
public void onStart(){
super.onStart();
}
// Simply indicate to change the text of the fragment
public void changeText(String newText){
mNewText=newText;
}
// Simply indicate to change the background color of the fragment
public void changeBG(int color) {
// If no color was passed, then set background to white
if(color == 0)
{
mNewColor=getResources().getColor(R.color.white);
}
// else set the color to what was passed in
else{
mNewColor=color;
}
}
}
MyAdapter
class MyAdapter extends FragmentPagerAdapter{
// Three simple fragments
BaseFragment fragA;
BaseFragment fragB;
BaseFragment fragC;
public MyAdapter(FragmentManager fm) {
super(fm);
}
public void setFragments(Context c){
// Set up the simple base fragments
fragA = new BaseFragment();
fragB = new BaseFragment();
fragC = new BaseFragment();
Resources res = c.getResources();
fragA.changeText("This is Fragment A!");
fragB.changeText("This is Fragment B!");
fragC.changeText("This is Fragment C!");
fragA.changeBG(res.getColor(R.color.dev_blue));
fragB.changeBG(res.getColor(R.color.dev_green));
fragC.changeBG(res.getColor(R.color.dev_orange));
}
#Override
public Fragment getItem(int position) {
// TODO: Make this more efficient, use a list or such, also comment more
Fragment frag = null;
if(position == 0){
frag = fragA;
}
else if(position == 1){
frag = fragB;
}
else if(position == 2){
frag = fragC;
}
return frag;
}
#Override
public int getCount() {
return 3;
}
}
You need to pass some sort of id along with newInstance() while creating instance. And according to that id you can use if..else to choose layout file.
See my reference code below:
int id;
public static Fragment newInstance(Context context, int id) {
FragmentTutorial f = new FragmentTutorial();
Bundle args = new Bundle();
this.id = id;
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(id == 1)
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment1, null);
else
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment2, null);
return root;
}
Can't you just introduce fields to the Fragment class to account for the variances in background, etc. and add them to its constructor? Then in getItem instantiate the Fragment class with different values depending on the value of position.
I am trying to implement Android Annotations to my View. But I cant figure out how to do it correctly. My Problem at the moment is that the fields in the View are always NULL.
I think I have a bit of an understanding problem how to use Android Annotations with Views and Adapters. Can someone give me a hint how I would do this correctly?
In my fragment I use the following adapter:
ItemAdapter
#EBean
public class ItemAdapter extends BaseAdapter {
public ItemAdapter(Context context) {
this.context = context;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
GalleryListView view;
if (convertView == null) {
view = GalleryListView_.build(context);
} else {
view = (GalleryListView) convertView;
}
imageUrls = getDocumentListAll();
DDocuments doc = documentProxy.getElementByDocument(imageUrls.get(position));
view.init(imageUrls.get(position), Uri.fromFile(new File(doc.getPath())));
// before I used annotations I did set my Image using this. But now I dont really know how to use this line
// ImageLoader.getInstance().displayImage(Uri.fromFile(new File(doc.getPath())).toString(), holder.image, options, animateFirstListener);
}
}
GalleryListView
#EViewGroup(R.layout.gallery_list)
public class GalleryListView extends LinearLayout {
#ViewById
ImageView image;
#ViewById
TextView text;
public void init(String imageText, Uri imageUri) {
text.setText(imageText);
image.setImageURI(imageUri);
}
}
The problem is that the Views have not been injected when you're making your call to init. So, a solution should be to not set the view's text directly; rather, you want to set a variable that you know will be read into the Views after they have been injected.
One way to do this is to make use of #AfterViews. A method with the #AfterViews annotation will be called after View injection has taken place.
Off the top of my head it would look like this:
#EViewGroup(R.layout.gallery_list)
public class GalleryListView extends LinearLayout {
String mText;
Uri mUri;
#ViewById
ImageView image;
#ViewById
TextView text;
public void init(String imageText, Uri imageUri) {
mText = imageText;
mUri = imageUri;
}
#AfterViews
void afterViews() {
text.setText(mText);
image.setImageURI(mUri);
}
}
This might not be the best solution, but it should get the job done. I suggest keeping the Android Annotation Cookbook handy; there are other similar annotations that will come in handy.
Where are you getting context from?
Try replacing
view = GalleryListView_.build(context);
with
view = new GalleryListView_(context);
In any case, for such a small view I don't see the profit of using Android Annotations, maybe if you had multiple resources I would understand, but for such little code, I recommend you to implement te constructor and inflate the resources yourself there.
I'm attempting to create a ListView where I can draw some basic shapes into each item in the ListView. I've found many examples using Views/Layouts within a ListView to add an image etc, but am unsure how I would draw into each one using a Canvas or similar. Furthermore these examples seem to all work in slightly different ways, so I was wondering what the best strategy is.
At the moment I just have a Main class which populates the ListView with basic text.
Main.java
public class Main extends Activity {
private ListView listView;
String[] list = {"One","Two","Three"};
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.main);
listView = (ListView)findViewById(R.id.ListView1);
listView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, list));
}
}
Now I gather I'll need change the Adapter bit here, and also write another class which possibly extends View, which I can draw to, then add this as an item in the ListView?
Thanks for your help in advance.
You would need to subclass your ListView to a custom implementation.
public class CustomListAdapter extends ArrayAdapter<CustomRowData> {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
CustomRowData currentRow = getItem(position);
View customRowView = .... // such as CustomRowView below.
...
...
return customRowView;
}
}
You can change the view for each row in the ListView by replacing the ... with the appropriate View code.
For example, you can use SurfaceView to draw on it.
class CustomRowView extends SurfaceView {
...
...
#Override
public void onDraw(Canvas canvas) {
// Use |canvas| to draw on..
}
}
The above code isn't tested, but hopefully will drive you to the right direction. Make sure you optimize your drawing such as use caching when available, preprocess, etc.