I'm trying to find a solution to pass data from my recyclerview adapter to new activity. I researched a lot but I didn't get a solution.
I know that I need to use putExtra() but I don't know how.
The data is taken from API, I have displayed in the CompaniesListActivity but in this activity I want to show only name of the company and in the Company description I want to show the others.
Like this:
Companies:
Company1
Company2
Company3
Company4
Company5
and when I click to one of them I will get
description
nipt
clubs
activity_company_description.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CompanyDescription">
<android.support.v7.widget.RecyclerView
android:id="#+id/company_description"
android:layout_width="395dp"
android:layout_height="715dp"
tools:layout_editor_absoluteX="8dp"
tools:layout_editor_absoluteY="8dp" />
</android.support.constraint.ConstraintLayout>
row_company_description.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="160dp"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/row_padding_vertical"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingBottom="#dimen/row_padding_vertical">
<TextView
android:id="#+id/tvCompanyDescription"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_marginStart="10dp"
android:layout_marginTop="3dp"
android:layout_marginEnd="8dp"
android:text="TextView"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.023"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvCompanyName" />
<TextView
android:id="#+id/tvCompanyNipt"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_marginStart="10dp"
android:layout_marginTop="3dp"
android:layout_marginEnd="8dp"
android:text="TextView"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvCompanyDescription" />
<TextView
android:id="#+id/tvClubs"
android:layout_width="wrap_content"
android:layout_height="15dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:text="TextView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvCompanyNipt"
app:layout_constraintVertical_bias="0.2"
tools:layout_editor_absoluteX="5dp" />
</android.support.constraint.ConstraintLayout>
my recyclerview adapter
private Context context;
public CompanyAdapter(Context context, Companies companies) {
this.companies = companies;
this.context = context;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int i) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.row_companies, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder viewHolder, final int position) {
final Company comp = companies.getCompanies().get(position);
viewHolder.compName.setText(comp.getCompany().getName());
viewHolder.compDesc.setText(comp.getCompany().getDescription());
viewHolder.compNipt.setText(comp.getCompany().getNipt());
viewHolder.compClubs.setText(comp.getClubs().toString());
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Company selectedCom = companies.getCompanies().get(position);
Intent intent = new Intent(context, CompanyDescription.class);
intent.putExtra("company", selectedCom);
intent.putExtra("description", comp);
intent.putExtra("nipt", comp);
intent.putExtra("clubs", comp);
context.startActivity(intent);
}
});
}
#Override
public int getItemCount() {
Integer rows = companies == null ? 0 : companies.getCompanies().size();
return rows;
}
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView compName, compDesc, compNipt, compClubs;
public ViewHolder(#NonNull View itemView) {
super(itemView);
compName = (TextView) itemView.findViewById(R.id.tvCompanyName);
compDesc = (TextView) itemView.findViewById(R.id.tvCompanyDescription);
compNipt = (TextView) itemView.findViewById(R.id.tvCompanyNipt);
compClubs = (TextView) itemView.findViewById(R.id.tvClubs);
}
}
my CompaniesListActivity->
private RecyclerView recyclerView;
private RecyclerView.Adapter adapter;
private UserService service;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_companies_list);
CompaniesRetriver companiesRetriver = new CompaniesRetriver();
this.recyclerView = (RecyclerView) findViewById(R.id.companies_list);
SharedPreferences editor = getSharedPreferences(MY_PREFERENCE, MODE_PRIVATE);
String token = editor.getString("token", "");
final Context sfdsf = this;
Callback<Companies> callback = new Callback<Companies>() {
#Override
public void onResponse(Call<Companies> call, Response<Companies> response) {
if(response.isSuccessful()) {
Companies companies = response.body();
CompanyAdapter adapter = new CompanyAdapter(CompaniesListActivity.this, companies);
recyclerView.setLayoutManager(new LinearLayoutManager(CompaniesListActivity.this));
recyclerView.setAdapter(adapter);
System.out.println(companies);
} else {
System.out.println(response.body());
}
}
#Override
public void onFailure(Call<Companies> call, Throwable t) {
System.out.println(t.getLocalizedMessage());
}
};
companiesRetriver.getCompanies(callback, token);
recyclerView.addItemDecoration(
new DividerItemDecoration(ContextCompat.getDrawable(getApplicationContext(),
R.drawable.item_separator)));
}
and CompanyDescriptionActivity
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_company_description);
Company ind = (Company) getIntent().getSerializableExtra("company");
System.out.println(ind);
CompaniesRetriver companiesRetriver = new CompaniesRetriver();
this.recyclerView = (RecyclerView) findViewById(R.id.company_description);
try this one hope its helpfull to you.
Intent intent = new Intent(context, CompanyDescription.class);
intent.putExtra("key", value);
context.startActivity(intent);
and in Another activity you can get data by this
String value=getIntent().getStringExtra("key");
What you're describing is a problem with your Separation of Concerns here.
Let's analyze what you have, shall we?
Your Code
You have 2 activities. One fetches a list of data from an API, and displays it in a list (which involves creating the adapter and satisfying all the requirements for the list to display). This activity also serves as your data repository to the rest of the activities, since you store this list here, in the list activity.
The other activity is intended to display the Details (or in your case "Description") of an Item (or in your example a Company).
Problem
You have no way for the ListActivity to directly pass a Company to CompanyDescription
What you want to do
Separate things into smaller things, so you can tell a thing only does "this thing and nothing more". A lot of things huh?
Use the framework tools at your disposal.
What I would do
CompanyListActivity should only be in charge of the list; to display it and construct it, that's all it needs to know about it.
CompanyListActivity will inflate the XML (for the RecyclerView), will create an adapter and will request the data to a repository (you have it called CompaniesRetriever) So Companies Retriever will let the activity know when there's data (you already did this, since you pass a callback). There is an issue with your implementation, if the user loads this list and then immediately hits back, I don't see any code in the CompanyListActivity to inform the retriever that the data is no longer needed (since the user left the activity). Keep an eye for that, because if you're passing any context to said "retriever", you may have a memory leak waiting to happen.
So far so good. Now you have your list. And here is where we both do things differently:
In my version of this app, CompaniesRetriever is a singleton class (there can be only one, like Highlander), and so instead of doing new CompaniesRetriever() in the Description activity, you simply use the same instance that the CompaniesList activity used.
This opens a new possibility, when the Retriever (which we cannot see because you didn't share the code) receives the API callback with the data and it's ready to pass it back to the CompaniesList (in the callback you have), it instead keeps a local copy of this data and then passes it along.
Now when the user TAPS an item on the list, instead of passing the whole thing, you simply pass the "id" of the Company (or something unique to identify it) to the next activity via the intent.putString("Id", selectedCompany.getId() (the id can be anything you want as long as it allows the next activity to uniquely identify it).
In the NEXT activity, you obtain the "selected company id" and you call your Retriever (which has a list of all the companies in memory), with a new method whose signature looks like: public Company getCompanyById(String id) (pseudo code obviosuly)
The all the retriever does is:
public Company getCompanyById(String id) {
listofCompanies.get(id) // I would use a hashMap for faster lookup
}
(if you use a list instead you will need to iterate until you find what you're looking for, if you have 1000 companies, that's fine, if you expect a HUGE number, consider a hashmap or a faster data structure)
In any case, the end result is that your new "Description" activity doesn't need to worry too much about anything, it receives an ID and it asks for the data.
This keeps both activities oblivious to the origin of the data, they are simply given the data they need (the flow is mostly in one direction, which is good). Activities ASK for data, don't supply it.
I hope this helps you re-think your code, you wouldn't need to make a lot of changes, for you already have most of the things in place.
How about the XML?!
I'm not 100% sure what you mean by accessing the XML from "there" (your words), but I think you don't need it, because what you were thinking was to read the XML from the company you are displaying in the list, and that is not possible (literally) and unneeded with the architecture I'm proposing (which, for the record, I didn't invent) :D
Hope this guides you in a better direction.
Good luck!
I would try to do it like this:
private Context context;
private onItemClickListener;
public CompanyAdapter(OnItemClickListener listener, Companies companies) {
this.companies = companies;
this.onItemClickListener = listener;
}
public interface OnItemClickListener {
void OnItemClick(Companies companies)
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int i) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.row_companies, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder viewHolder, final int position) {
final Company comp = companies.getCompanies().get(position);
viewHolder.bind(comp, onItemClickListener);
}
#Override
public int getItemCount() {
Integer rows = companies == null ? 0 : companies.getCompanies().size();
return rows;
}
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView compName, compDesc, compNipt, compClubs;
public ViewHolder(#NonNull View itemView) {
super(itemView);
compName = (TextView) itemView.findViewById(R.id.tvCompanyName);
compDesc = (TextView) itemView.findViewById(R.id.tvCompanyDescription);
compNipt = (TextView) itemView.findViewById(R.id.tvCompanyNipt);
compClubs = (TextView) itemView.findViewById(R.id.tvClubs);
}
public void bind (Companies comp, OnItemClickListener listener) {
compName.setText(comp.getCompany().getName());
compDesc.setText(comp.getCompany().getDescription());
compNipt.setText(comp.getCompany().getNipt());
compClubs.setText(comp.getClubs().toString());
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onItemClickListener.OnItemClick(comp);
Context context = v.getContext;
Intent intent = new Intent(context, CompanyDescription.class);
intent.putExtra("company", selectedCom);
intent.putExtra("description", comp);
intent.putExtra("nipt", comp);
intent.putExtra("clubs", comp);
context.startActivity(intent);
}
});
}
}
With so much confusion I found a simple way how to display the data, I decided not use recyclerview but something simpler. All is working fine now.
Intent intent = getIntent();
company = (Company) intent.getSerializableExtra("company");
tvCompanyName = (TextView) findViewById(R.id.tvCompanyName);
tvCompanyDescription = (TextView) findViewById(R.id.tvCompanyDescription);
tvCompanyNipt = (TextView) findViewById(R.id.tvCompanyNipt);
tvCompanyName.setText(company.getCompany().getName());
tvCompanyDescription.setText(company.getCompany().getDescription());
tvCompanyNipt.setText(company.getCompany().getNipt());
Related
I got a problem where my codes do not show any error but the data won't come out in the RecyclerView. Every time I run it, it displays blank at the RecyclerView section. I have set my database rule to allow read, write: if true; but still didn't work.
Here's Java codes:
public class diary_user extends AppCompatActivity {
RecyclerView recyclerView;
Adapter adapter;
ImageView add;
DrawerLayout drawerLayout;
NavigationView navigationView;
Toolbar toolbar;
TextView date, note, time;
FirestoreRecyclerAdapter adapters;
FirebaseFirestore fStore;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_diary_user);
date = findViewById(R.id.date_text);
time = findViewById(R.id.time_text);
note = findViewById(R.id.note_textview);
recyclerView = findViewById(R.id.recycleView1);
fStore = FirebaseFirestore.getInstance();
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
final String current = user.getUid();
Query query = fStore.collection("Diary").whereEqualTo("UID",current);
FirestoreRecyclerOptions<ModelClass> options = new FirestoreRecyclerOptions.Builder<ModelClass>()
.setQuery(query, ModelClass.class)
.build();
adapters = new FirestoreRecyclerAdapter<ModelClass, ModelViewHolder>(options) {
#NonNull
#Override
public ModelViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_diary, parent,
false);
return new ModelViewHolder(view);
}
#Override
protected void onBindViewHolder(#NonNull ModelViewHolder holder, int position, #NonNull
ModelClass model) {
holder.date.setText(model.getDate());
holder.note.setText(model.getText());
holder.time.setText(model.getTime());
holder.divider.setText(model.getDivider());
}
};
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapters);
}
private class ModelViewHolder extends RecyclerView.ViewHolder {
TextView date, time, note, divider;
public ModelViewHolder(#NonNull View itemView) {
super(itemView);
date = itemView.findViewById(R.id.date_text);
time = itemView.findViewById(R.id.time_text);
note = itemView.findViewById(R.id.note_textview);
divider = itemView.findViewById(R.id.line_textview);
}
}
#Override
protected void onStop() {
super.onStop();
if(adapters != null) {
adapters.stopListening();
}
}
#Override
protected void onStart() {
super.onStart();
if(adapters != null) {
adapters.startListening();
}
}
}
ModelClass codes:
public class ModelClass {
String date,time,text,divider;
ModelClass(String date,String time,String text,String divider){
this.date = date;
this.time = time;
this.text = text;
this.divider = divider;
}
public String getDate() {
return date;
}
public String getTime() {
return time;
}
public String getText() {
return text;
}
public String getDivider() {
return divider;
}
}
XML codes:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#ffffff">
<TextView
android:id="#+id/date_text"
android:layout_width="120dp"
android:layout_height="30dp"
android:layout_marginLeft="30dp"
android:layout_marginTop="20dp"
android:text="16/3/2021"
android:textColor="#000000"
android:textSize="20sp" />
<TextView
android:id="#+id/time_text"
android:layout_width="80dp"
android:layout_height="30dp"
android:layout_marginLeft="290dp"
android:layout_marginTop="20dp"
android:text="1:30PM"
android:textSize="14sp" />
<TextView
android:id="#+id/note_textview"
android:layout_width="200dp"
android:layout_height="30dp"
android:layout_marginLeft="30dp"
android:layout_marginTop="55dp"
android:text="Feeling good today"
android:textSize="16sp" />
<TextView
android:id="#+id/line_textview"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_below="#+id/time_text"
android:layout_marginLeft="30dp"
android:layout_marginTop="25dp"
android:text="_____________________________________________"
android:textColor="#CDC7C7"
android:textSize="16sp" />
</RelativeLayout>
Here's database structure:
add adapters.startListening(); this line mentioned below
adapters.startListening();
recyclerView.setHasFixedSize(true);
also check firestore rules
allow read, write : if true;
also check data collection field name and your model class variable name or you can your anotaion
#PropertyName("Date")
I have set my database rule to allow read, write: if true; but still didn't work.
If you set:
allow read, write: if true;
In your Security Rules, it means that you allow both, read and write operations, to be performed in your Cloud Firestore database. But this is not the reason why your RecylerView is empty. When you try to map a document from Firestore into an object of your "ModelClass", the name of the fields that exist in your class must match the name of your properties that exist in your database. Unfortunately, in your case, the fields don't match. See, the fields in your class start with lowercase, while in the database start with a capital letter, which is not correct.
To solve this, you have two options, you either change the name of your properties in the database to match the one in the class, or you can use an annotation in front of the getters. For example, if you have a field called "date" and the property in the database is called "Date" (upper-case D), your getter should look like this:
#PropertyName("Date")
public String getDate() {
return date;
}
In this way, you tell the compiler to look for a property called "Date" and not "date".
Because you are using public getters, don't also forget to set the fields in your class as private. If you want to keep them, for example, public, the getters are not needed. You can set the values directly on the public fields. So a minimum class declaration might look like this:
public class ModelClass {
public String date, time, text, divider;
}
Please also note, that the public no-argument constructor is also not needed, as it is provided by the compiler.
I am trying to code a rotating menu for a children's app. The idea is to allow kids to change between different scenarios, each containing different animals.
The animals are represented on a round surface and, as the kids swipe right or left, the globe rotates with old animals fading out and new ones fading in, kinda like this:
The result I'm trying to reach is similar to the iOS iCarousel wheel effect.
Toxic Bakery's ViewPagerTransforms library (https://github.com/ToxicBakery/ViewPagerTransforms) has an effect called Rotate Down that is very similar, but I haven't been able to adjust it to my needs. The pages roll in on their own instead of rotating on a common axis.
I've also tried the CursorWheelLayout (https://github.com/BCsl/CursorWheelLayout), but there are many performance issues due to the images, making the app crash.
My most recent attempt has been the SpinMenu (https://github.com/Hitomis/SpinMenu), which is great. The fragments do rotate on a common axis, but only while zoomed out. I haven't figured out a way of making it change pages (as a ViewPager would) with the fragments rotating in and out of view.
Any suggestions on what to do to reach the desired result?
Here's the solution I found:
1) Implement the CarouselView library: https://gtomato.github.io/carouselview/
2) As CarouselView extends from RecyclerView, create the layout for the CarouselView to inflate (which I called carouselview_item):
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="#+id/imageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_launcher_background"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
3) As CarouselView extends from RecyclerView, create a CarouselViewAdapter extending from RecyclerView.Adapter
public class CarouselViewAdapter extends RecyclerView.Adapter<CarouselViewAdapter.ViewHolder> {
private List<Fragment> list;
public CarouselViewAdapter(List<Fragment> list) {
this.list = list;
}
#NonNull
#Override
public CarouselViewAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.carouselview_item, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull CarouselViewAdapter.ViewHolder holder, int position) {
Fragment fragment = list.get(position);
holder.bind(object);
}
#Override
public int getItemCount() {
return list.size();
}
// In case you are using, let's say, RxJava for getting the list
public void setObjects(List<Fragment> list) {
if (list.size() == 0) {
this. = list;
} else {
this.list.addAll(list);
notifyDataSetChanged();
}
}
public class ViewHolder extends RecyclerView.ViewHolder {
private T example;
public ViewHolder(View itemView) {
super(itemView);
// Find your views:
// example = itemView.findViewById(R.id.example);
}
public void bind(T object){
// Bind the info you need to the view:
// example.setInfo(object.getInfo)
}
}
}
}
4) Set things up on the activity, including the desired Transformer and also the adapter:
public class MainActivity extends AppCompatActivity {
private CarouselView carouselView;
private List<Fragment> list;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initAssets();
Fragment object1 = new T(/* necessary parameters, if any*/);
Fragment object2 = new T(/* necessary parameters, if any*/);
Fragment object3 = new T(/* necessary parameters, if any*/);
list.add(object1);
list.add(object2);
list.add(object3);
carouselView.setTransformer(new WheelViewTransformer());
carouselView.setAdapter(new CarouselViewAdapter(list));
}
private void initAssets() {
carouselView = findViewById(R.id.carouselview);
list = new ArrayList<>();
}
}
It worked for me like that, although the performance isn't great due to the images I am using for now.
Enjoy!
I'm creating a simple chess clock -type timer app. I'm trying to show the players and the time they have left as rows in a ListView. I'm using a custom view that extends RelativeLayout for these rows, so that I can give it methods that highlight the player in turn, for example.
Row layout class:
public class GameTimerView extends RelativeLayout {
private TextView nameView;
private TextView timerView;
public GameTimerView(Context context) {
super(context);
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.timer_view, this);
loadViews();
}
...
private void loadViews() {
nameView = (TextView)findViewById(R.id.nameView);
timerView = (TextView)findViewById(R.id.timerView);
}
public void setName(String name) {
nameView.setText(name);
}
public void setTime(long timeInMillis) {
timerView.setText(String.format("%02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(timeInMillis),
TimeUnit.MILLISECONDS.toSeconds(timeInMillis) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(timeInMillis))
));
}
public void setActive() {
this.nameView.setTextColor(Color.GREEN);
}
public void setInactive() {
nameView.setTextColor(Color.BLACK);
}
}
Row layout XML (timer_view.xml):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="#+id/nameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="#+id/timerView"
android:text="#string/player_default_name" />
<TextView
android:id="#+id/timerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="#string/zero_time" />
</RelativeLayout>
Adapter:
public class playerArrayAdapter extends ArrayAdapter<Player> {
private final Context context;
private final ArrayList<Player> players;
public playerArrayAdapter(Context context, ArrayList<Player> values) {
super(context, R.layout.timer_view, values);
this.context = context;
this.players = values;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
GameTimerView playerView = new GameTimerView(context);
players.get(position).setTimerView(playerView);
return playerView;
}
}
Player class setTimerView function:
public void setTimerView(GameTimerView timer) {
this.timerView = timer;
this.timerView.setName(this.name);
this.timerView.setTime(this.totalCountDown);
this.timerView.setInactive();
}
In the activity's onCreate method:
playerArrayAdapter playersAdapter = new playerArrayAdapter(
getApplicationContext(),
game.getPlayers()
);
ListView playersView = (ListView) findViewById(R.id.playerList);
playersView.setAdapter(playersAdapter);
At first this seems to work, and the desired player names and times are rendered to the list properly. However, if I later programmatically call for example Player.timerView.setActive(), nothing happens.
Having looked at dozens of examples of custom adapters for ListViews none of them seems to be using it this way - the view is always inflated directly in Apdater.getView(). I want the flexibility of an extended view class however, but apparently I'm doing something wrong.
So, what's the correct way to use custom view class for ListView rows?
First, when inflating your GameTimerView, you've got a RelativeLayoutinside another.
Second, to answer the question : it might be good not to re-create a view on each call to ArrayAdapter.getView(), but instead modify playerArrayAdapter by adding a cache like this :
private List<View> views;
public playerArrayAdapter(Context context, ArrayList<Player> values) {
super(context, R.layout.timer_view, values);
views = new ArrayList<View>(values.length);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View playerView;
if (position < views.size()) {
playerView = views.get(position);
if (playerView != null)
return playerView;
} else {
while (views.size() < position)
views.add(null);
}
playerView = new GameTimerView(context);
views.add(position, playerView);
players.get(position).setTimerView(playerView);
return playerView;
}
If this successfully corrects your problem, it means that previously, when setting a player as active the ListView was getting all views again for rendering, recreating them, and doing so, erasing any previous state.
As per your getView() method of Adapter, it will create a new view always that might have causing you issue.
If you really want to implement a CustomViewGroup then please refer this good implementation of the custom view here. Hope this will help you to start.
I want my ListView to contain buttons, but setting the button's xml property, onClick="myFunction" and then placing a public void myFunction(android.view.View view) method in the activity causes an NoSuchMethodException (the stack trace is null) to be thrown, as although the onclick listener is there, it doesn't fire myFunction(...) and cause the activity to close.
How do I create a custom Adapter that connects a View.OnClickListener to a button on each row of a ListView?
My ListView is created as follows...
[activity.java content..]
public void myFunction(android.view.View view)
{
//Do stuff
}
[activity.xml content..]
<LinearLayout xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".FrmCustomerDetails" >
<ListView android:id="#+id/LstCustomerDetailsList" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" android:clickable="true" android:clipChildren="true" android:divider="#null" android:dividerHeight="0dp" android:fastScrollEnabled="true" android:footerDividersEnabled="false" android:headerDividersEnabled="false" android:requiresFadingEdge="vertical" android:smoothScrollbar="true" />
</LinearLayout>
[activity_row_item.xml content..]
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="#+id/Llt" android:layout_width="match_parent" android:layout_height="match_parent" >
<Button android:id="#+id/Btn" android:text="Click me" android:onClick="myFunction" />
</LinearLayout>
Here is how to create the custom Adapter, connecting View.OnClickListener to a ListView with a button per row...
1. Create a layout for a typical row
In this case, the row is composed of three view components:
name (EditText)
value (EditText:inputType="numberDecimal")
delete (Button)
Xml
pay_list_item.xml layout is as follows:
<?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" >
<EditText
android:id="#+id/pay_name"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="2"
android:hint="Name" />
<EditText
android:id="#+id/pay_value"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1"
android:inputType="numberDecimal"
android:text="0.0" />
<Button
android:id="#+id/pay_removePay"
android:layout_width="100dp"
android:layout_height="fill_parent"
android:text="Remove Pay"
android:onClick="removePayOnClickHandler" />
</LinearLayout>
Note: the button has onClick handler defined in xml layout file, because we want to refer its action to a specific list item.
Doing this means that the handler will be implemented in Activity file and each button will know which list item it belongs to.
2. Create list item adapter
This is the java class that is the controller for pay_list_item.xml.
It keeps references for all of its views, and it also puts these references in tags, extending the ArrayAdapter interface.
The Adapter:
public class PayListAdapter extends ArrayAdapter<Payment> {
private List<Payment> items;
private int layoutResourceId;
private Context context;
public PayListAdapter(Context context, int layoutResourceId, List<Payment> items) {
super(context, layoutResourceId, items);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.items = items;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
PaymentHolder holder = null;
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new PaymentHolder();
holder.Payment = items.get(position);
holder.removePaymentButton = (ImageButton)row.findViewById(R.id.pay_removePay);
holder.removePaymentButton.setTag(holder.Payment);
holder.name = (TextView)row.findViewById(R.id.pay_name);
holder.value = (TextView)row.findViewById(R.id.pay_value);
row.setTag(holder);
setupItem(holder);
return row;
}
private void setupItem(PaymentHolder holder) {
holder.name.setText(holder.Payment.getName());
holder.value.setText(String.valueOf(holder.Payment.getValue()));
}
public static class PaymentHolder {
Payment Payment;
TextView name;
TextView value;
ImageButton removePaymentButton;
}
}
Here we list the Payment class items.
There are three most important elements here:
PayListAdapter constructor: sets some private fields and calls superclass constructor. It also gets the List of Payment objects. Its implementation is obligatory.
PaymentHolder: static class that holds references to all views that I have to set in this list item. I also keep the Payment object that references to this particular item in list. I set it as tag for ImageButton, that will help me to find the Payment item on list, that user wanted to remove
Overriden getView method: called by superclass. Its goal is to return the single List row. We create its fields and setup their values and store them in static holder. Holder then is put in row’s tag element. Note that there is a performance issue, as the row is being recreated each time it is displayed. I used to add some flag in holder like isCreated, and set it to true after row was already created. then you can add if statement and read tag’s holder instead of creating it from scratch.
Payment.java is quite simple as for now and it looks a bit like BasicNameValuePair:
public class Payment implements Serializable {
private String name = "";
private double value = 0;
public Payment(String name, double value) {
this.setName(name);
this.setValue(value);
}
...
}
There are additional gets and sets for each private field not shown.
3. Add ListView to the activity layout xml file
In its simpliest form, it will be enough to add this view to activity layout:
<ListView
android:id="#+id/EnterPays_PaysList"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</ListView>
4. Set up adapter to this list view in Activity Java code
In order to display items in ListView you need to set up its adapter and map it to some other ArrayList of Payment objects (as I am extending an Array adapter here). Here is code that is responsible for binding adapter to editPersonData.getPayments() ArrayList:
PayListAdapter adapter = new PayListAdapter(AddNewPerson.this, R.layout.pay_list_item, editPersonData.getPayments());
ListView PaysListView = (ListView)findViewById(R.id.EnterPays_PaysList);
PaysListView.setAdapter(adapter);
5. Adding / removing items to ListView (and its adapter)
Adapter is handled just like any other ArrayList, so adding new element to it is as simple as:
Payment testPayment = new Payment("Test", 13);
adapter.add(testPayment);
adapter.remove(testPayment);
6. Handle Remove Payment button click event
In an activity’s code, where ListView is displayed, add public method that will handle remove button click action. The method name has to be exactly the same as it was in pay_list_item.xml:
android:onClick="removePayOnClickHandler"
The method body is as follows:
public void removePayOnClickHandler(View v) {
Payment itemToRemove = (Payment)v.getTag();
adapter.remove(itemToRemove);
}
The Payment object was stored in ImageButton’s Tag element. Now it is enough to read it from Tag, and remove this item from the adapter.
7. Incorporate remove confirmation dialog window
Probably you need also make sure that user intentionally pressed the remove button by asking him additional question in confirmation dialog.
Dialogue
a) Create dialog’s id constant
This is simply dialog’s ID. it should be unique among any other dialog window that is handled by current activity. I set it like that:
protected static final int DIALOG_REMOVE_CALC = 1;
protected static final int DIALOG_REMOVE_PERSON = 2;
b) Build dialog
I use this method to build dialog window:
private Dialog createDialogRemoveConfirm(final int dialogRemove) {
return new AlertDialog.Builder(getApplicationContext())
.setIcon(R.drawable.trashbin_icon)
.setTitle(R.string.calculation_dialog_remove_text)
.setPositiveButton(R.string.calculation_dialog_button_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
handleRemoveConfirm(dialogRemove);
}
})
.setNegativeButton(R.string.calculation_dialog_button_cancel, null)
.create();
}
AlertDialog builder pattern is utilized here. I do not handle NegativeButton click action – by default the dialog is just being hidden. If dialog’s confirm button is clicked, my handleRemoveConfirm callback is called and action is performed based on dialog’s ID:
protected void handleRemoveConfirm(int dialogType) {
if(dialogType == DIALOG_REMOVE_PERSON){
calc.removePerson();
}else if(dialogType == DIALOG_REMOVE_CALC){
removeCalc();
}
}
c) Show Dialog
I show dialog after my remove button click. The showDialog(int) is Android’s Activity’s method:
OnClickListener removeCalcButtonClickListener = new OnClickListener() {
public void onClick(View v) {
showDialog(DIALOG_REMOVE_CALC);
}
};
the showDialog(int) method calls onCreateDialog (also defined in Activity’s class). Override it and tell your app what to do if the showDialog was requested:
#Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_REMOVE_CALC:
return createDialogRemoveConfirm(DIALOG_REMOVE_CALC);
case DIALOG_REMOVE_PERSON:
return createDialogRemoveConfirm(DIALOG_REMOVE_PERSON);
}
}
Take a look at this blog post I wrote on exactly this matter:
Create custom ArrayAdapter
There are comments that explain every action I make in the adapter.
Here is the explanation in short:
So lets for example take a row where you want to place a CheckBox, ImageView
and a TextView while all of them are clickable. Meaning that you can click the
row it self for going to another Actvity for more details on the row, check its
CheckBox or press the ImageView to perform another operation.
So what you should do is:
1. First create an XML layout file for your ListView row:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<CheckBox
android:id="#+id/cbCheckListItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp" />
<TextView
android:id="#+id/tvItemTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="item string" />
<ImageView
android:id="#+id/iStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:contentDescription="#drawable/ic_launcher"
android:src="#drawable/ic_launcher" />
</LinearLayout>
2. Second in your java code define a ViewHolder, a ViewHolder
is designed to hold the row views and that way operating more quickly:
static class ViewHolder
{
TextView title;
CheckBox checked;
ImageView changeRowStatus;
}
3. Now we have to define CustomArrayAdapter, using the array adapter
we can define precisely what is the desired output for each row based on the content of this
row or it’s position. We can do so by overriding the getView method:
private class CustomArrayAdapter extends ArrayAdapter<RowData>
{
private ArrayList<RowData> list;
//this custom adapter receives an ArrayList of RowData objects.
//RowData is my class that represents the data for a single row and could be anything.
public CustomArrayAdapter(Context context, int textViewResourceId, ArrayList<RowData> rowDataList)
{
//populate the local list with data.
super(context, textViewResourceId, rowDataList);
this.list = new ArrayList<RowData>();
this.list.addAll(rowDataList);
}
public View getView(final int position, View convertView, ViewGroup parent)
{
//creating the ViewHolder we defined earlier.
ViewHolder holder = new ViewHolder();)
//creating LayoutInflator for inflating the row layout.
LayoutInflater inflator = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//inflating the row layout we defined earlier.
convertView = inflator.inflate(R.layout.row_item_layout, null);
//setting the views into the ViewHolder.
holder.title = (TextView) convertView.findViewById(R.id.tvItemTitle);
holder.changeRowStatus = (ImageView) convertView.findViewById(R.id.iStatus);
holder.changeRowStatus.setTag(position);
//define an onClickListener for the ImageView.
holder.changeRowStatus.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
Toast.makeText(activity, "Image from row " + position + " was pressed", Toast.LENGTH_LONG).show();
}
});
holder.checked = (CheckBox) convertView.findViewById(R.id.cbCheckListItem);
holder.checked.setTag(position);
//define an onClickListener for the CheckBox.
holder.checked.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
//assign check-box state to the corresponding object in list.
CheckBox checkbox = (CheckBox) v;
rowDataList.get(position).setChecked(checkbox.isChecked());
Toast.makeText(activity, "CheckBox from row " + position + " was checked", Toast.LENGTH_LONG).show();
}
});
//setting data into the the ViewHolder.
holder.title.setText(RowData.getName());
holder.checked.setChecked(RowData.isChecked());
//return the row view.
return convertView;
}
}
4. Now you need to set this adapter, as the adapter of your ListView.
this ListView can be created in java or using an XML file, in this case I’m using a list that was
defined in the XML file using the “list” id:
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
ListView list = (ListView)findViewById(R.id.list);
CustomArrayAdapter dataAdapter = new CustomArrayAdapter(this, R.id.tvItemTitle, rowDataList);
list.setAdapter(dataAdapter);
}
5. Finally if we want to be able to press the row it self and not only a certain view in it
we should assign an onItemClickListener to the ListView:
list.setOnItemClickListener(new OnItemClickListener()
{
public void onItemClick(AdapterView<?> parent, View view,int position, long id)
{
Toast.makeText(activity, "row " + position + " was pressed", Toast.LENGTH_LONG).show();
}
});
First, the way of adding listeners in xml using onClick="function" is deprecated. You need a ViewHolder class to link the button in the xml to your java code. Then you can implement onClickListener for that.
Inside your getView() implementation of CustomAdapter, you can try like below.
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.xxxxx, null);
holder = new ViewHolder();
holder.invite = (Button) convertView.findViewById(R.id.button);
} else {
holder = (ViewHolder) convertView.getTag();
}
final int pos = position;
holder.button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
handleClick(pos);
}
});
}
class ViewHolder {
Button button;
}
I'm developing an Android app in which I've got some lists. It's more complex than I'll explain here, but the scenario I'm posting behaves unexpectedly as well.
I've been browsing several questions in Stack Overflow but none that I found solves this issue.
So, for the sake of the question I've built this scenario. I've got an Activity with an array of Strings, like so:
public class TestViewlazyloadActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
String[] items = { "lorem", "ipsum", "dolor", "sit", "amet" };
ListView list = (ListView) findViewById(R.id.testListView);
TestAdapter adapter = new TestAdapter(this, R.layout.list_item, items);
list.setAdapter(adapter);
}
}
Being my two layouts this way:
<?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" >
<ListView
android:id="#+id/testListView"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
</ListView>
</LinearLayout>
And this way:
<?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" >
<TextView
android:id="#+id/testTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
Pretty simple. Then I've implemented an ArrayAdapter class with the ViewHolder idea, which getView() method is as follows:
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
String item = this.getItem(position);
if(convertView == null) {
LayoutInflater inflater = ((Activity) this.getContext()).getLayoutInflater();
convertView = inflater.inflate(R.layout.list_item, null);
holder = new ViewHolder();
holder.textView = (TextView) convertView.findViewById(R.id.testTextView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
final ViewHolder auxHolder = holder;
LoadingService.capitalizeItemWithDelay(item, new LoadedCallback() {
public void onItemLoaded(String item) {
auxHolder.textView.setText(item);
}
});
return convertView;
}
Being LoadedCallback an interface with just onItemLoaded. And LoadingService.capitalizeItemWithDelay() just starts a Thread which sleeps for a random amount of time (to simulate a lazy load) and calls this callback with the upper case String.
Normal behavior should be that user is viewing the list with 5 blank rows. After some time items begin to spawn in their respective rows, even in a random order given the random sleep timer.
What really happens is that items get loaded in the first row, one by one, and eventually some of them get set in their real place. It's like the auxHolder keeps the reference in its TextView to the first element, which is weird. I understand it's either because of the view being recycled or because of the auxHolder being final.
Any ideas? Thanks in advance.
Get rid of final on your ViewHolder variable and move ViewHolder auxHolder into the inner class.
Try this:
...
LoadingService.capitalizeItemWithDelay(item, new LoadedCallback() {
ViewHolder auxHolder = holder;
public void onItemLoaded(String item) {
auxHolder.textView.setText(item);
When you use the final key word with a local variable, like auxHolder, you are saying that the value you give it, is the last value it will have. This is why you are having problems. Here is a link that will explain a little better: http://www.javapractices.com/topic/TopicAction.do?Id=23
You may not be updating on the UI Thread.
I would try performing your loading actions in a post and see that fixes everything.
Try doing something like this
convertView.post(new Runnable() {
#Override
public void run() {
LoadingService.capitalizeItemWithDelay(item, new LoadedCallback() {
ViewHolder auxHolder = holder;
public void onItemLoaded(String item) {
auxHolder.textView.setText(item);
}
}
}
});