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!
Related
I have a custom ArrayAdapter of a base class. I also have a subclass and I want the ArrayAdapter to be able to receive two different types of data i.e. (arrayList and arraylist)and display them consecutively in one seamless listview.
I have never done this before so general comments and guidelines are much appreciated. I tried to make the ArrayAdapter generic:
public class MyAdapter<T extends BaseClass> extends ArrayAdapter<T>
with this constructor:
public MyAdapter(Context context, ArrayList<T> items) {
super(context, 0, items);
fullList = items;
}
But I do not know how to then pass a generic ArrayList that can either have type subclass or base class. (and this approach will later involve code that uses instanceOf which is not the best way to do things ... as I have learned from SO)
Instead of generics, I was thinking of extending this custom arrayadapter itself. But I am not sure how this would work.
What is the best way to approach this? Is there maybe a better adapter suited for this case other than arrayadapter. Or could it be that recyclerview has a solution to this problem?
I ultimately want to have two similar but different types of datasets (one subclassed another), outputted as one seamless listview. I was not able to find the general approach to doing this.
What datatypes are you using in your ArrayLists?
If, for instance, Strings and Integers, could you not convert the values and merge into a single ArrayList?
for (int i=0; i<arraylist1.size(); i++) { //arraylist1 containing integers
arraylist2.add(Integer.toString(arraylist1.get(i));
}
then you can just apply the one ArrayList to a standard ArrayAdapter.
You can solve this problem using view type.
example:
row_type_one.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/txtTypeOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
row_type_two.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:id="#+id/logo"
android:layout_width="100dp"
android:layout_height="match_parent" />
<TextView
android:id="#+id/txtTypeOne"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
YourTypeOne.java
public class YourTypeOne{
public String title;
}
YourTypeTow.java
public class YourTypeTow {
public String title;
public String imgUrl;
}
YourAdapter.java
public class YourAdapter extends RecyclerView.Adapter<YourAdapter.YourViewHolder> {
ArrayList<Object> objects = new ArrayList<>();
public YourAdapter(ArrayList<YourTypeOne> typeOneArray, ArrayList<YourTypeTow> typeTowArray) {
objects.addAll(typeOneArray);
objects.addAll(typeTowArray);
}
#Override
public int getItemViewType(int position) {
return objects.get(position) instanceof YourTypeOne ? 1 : 2;
}
#NonNull
#Override
public YourViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v;
if (viewType == 1) {
v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.row_type_one, parent, false);
} else {
v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.row_type_two, parent, false);
}
return new YourViewHolder(v,viewType);
}
#Override
public void onBindViewHolder(#NonNull YourViewHolder holder, int position) {
}
#Override
public int getItemCount() {
return objects.size();
}
class YourViewHolder extends RecyclerView.ViewHolder {
private TextView txtTypeOne;
private TextView txtDescription;
private ImageView logo;
public YourViewHolder(#NonNull View itemView, int vewType) {
super(itemView);
if (vewType==1){
//find views by ides in .xml
txtTypeOne = itemView.findViewById(R.id.txtTypeOne);
}else {
//find views by ides in row_type_two.xml
txtDescription = itemView.findViewById(R.id.txtDescription);
logo = itemView.findViewById(R.id.logo);
}
}
public void bindeData(Object object){
if (object instanceof YourTypeOne){
//set data to layout row_type_one
}else {
//set data to layout row_type_two
}
}
}
}
good luck. :)
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());
I have a problem. I have a viewpager with 3 fragments inside. In first fragment i have some ImageViews.
First of all how make that imageviews visible with timer? I used thise code below but i have error which looks like: variable 'mImageView' is accessed from within inner class, needs to be declared class.
mImageView.setVisibility(View.INVISIBLE);
mImageView.postDelayed(new Runnable() {
public void run() {
mImageView.setVisibility(View.VISIBLE);
}
}, 5000);
How can i solve this problem?
Second I tried to move that elements (ImageViews) by X values when user start scrolling from first fragment to next fragment. It works but when i go to last 3-d fragment app crash. So why it happen?!
MainActivity.java
pager.setPageTransformer(false, new ViewPager.PageTransformer() {
#Override
public void transformPage(View page, float position) {
// transformation here
final float normalizedPosition = Math.abs(Math.abs(position) - 1);
page.setAlpha(normalizedPosition);
int pageWidth = page.getWidth();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
page.setAlpha(0);
} else if (position <= 1) { // [-1,1]
ImageView mImageView = (ImageView)findViewById(R.id.imageView2);
mImageView.setTranslationX((float) (-(1 - position) * 1.7 * pageWidth));
mImageView.setVisibility(View.INVISIBLE);
mImageView.postDelayed(new Runnable() {
public void run() {
mImageView.setVisibility(View.VISIBLE);
}
}, 5000);
// The 0.5, 1.5 values you see here are what makes the view move in a different speed.
// The bigger the number, the faster the view will translate.
// The result float is preceded by a minus because the views travel in the opposite direction of the movement.
}
else{ // (1,+Infinity]
// This page is way off-screen to the right.
page.setAlpha(0);
}
}
});
Third: Is it possible to make move elements by circle when user scroll. Need any help!
For your first question, as I said in the comment, you need to make the mImageView variable final
final ImageView mImageView = (ImageView)findViewById(R.id.imageView2);
Then, the null pointer exception, is probably caused (as Blackbelt said), because you're using the activity's findViewById method, and probably the imageView you need is in the fragment view:
final ImageView mImageView = (ImageView) page.findViewById(R.id.imageView2);
And for your 3ยบ question, please explain what you mean by "move by circle", then I'll update my post(if I can) with an answer.
MainActivity.java
// Initialize the ViewPager and set an adapter
ViewPager pager = (ViewPager) findViewById(R.id.pager);
pager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager()));
ViewPagerAdapter.java
public class ViewPagerAdapter extends FragmentStatePagerAdapter {
private final int PAGES = 3;
private String[] titles={"News", "Organizations", "Map"};
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new TabFragment1();
case 1:
return new TabFragment2();
case 2:
return new TabFragment3();
default:
throw new IllegalArgumentException("The item position should be less or equal to:" + PAGES);
}
}
#Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
#Override
public int getCount() {
return PAGES;
}
}
As you see for each page I have individual fragment documents. Here below one of them:
public class TabFragment1 extends Fragment{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_tab_1, container, false);
}
}
fragment_tab_1.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="match_parent" >
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="#drawable/city"
android:id="#+id/imageView2"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
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'm trying to create my first Android app that looks like following: there is main activity with multiple fragments initialized by FragmentPagerAdapter. There is another activity (SettingsActivity) where I want to list all the fragment names and allow hiding some of them. To hide them I want to use the following:
FragmentManager fm=getFragmentManager();
Fragment myFragment=fm.findFragmentByTag("tag");
fm.beginTransaction().hide(myFragment).commit();
The problem is that I don't know fragment id or tag, not sure if they exist. How I can get them? Should I switch to XML definition to make it possible?
Adapter:
public class TabsPagerAdapter extends FragmentPagerAdapter {
public TabsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
switch (index){
case 0:
return new CoverFragment();
case 1:
return new NumbersConverterFragment();
case 2:
return new TempConverterFragment();
case 3:
return new LengthConverterFragment();
case 4:
return new AreaConverterFragment();
case 5:
return new VolumeConverterFragment();
case 6:
return new WeightConverterFragment();
case 7:
return new SpeedConverterFragment();
}
return null;
}
#Override
public int getCount() {
return 8;
}
Main activity:
public class MainActivity extends FragmentActivity implements ActionBar.TabListener {
private ViewPager viewPager;
private TabsPagerAdapter tabsPagerAdapter;
private ActionBar actionBar;
#Override
protected void onCreate(Bundle savedInstanceState) {
String[] tabs={getString(R.string.title_section0), getString(R.string.title_section1),getString(R.string.title_section2)};
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager=(ViewPager) findViewById(R.id.pager);
actionBar=getActionBar();
tabsPagerAdapter=new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(tabsPagerAdapter);
actionBar.setHomeButtonEnabled(false);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
for(String tab : tabs){
actionBar.addTab(actionBar.newTab().setText(tab).setTabListener(this));
}
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
// on changing the page
// make respected tab selected
actionBar.setSelectedNavigationItem(position);
}
...
});
}
Fragment layout:
<?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"
android:background="#fbfdfb"
>
<TextView android:text="#string/celsius_" android:id="#+id/textView1"
android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<EditText android:text="" android:id="#+id/txtCelsius" android:layout_width="match_parent"
android:layout_height="wrap_content"></EditText>
<TextView android:text="#string/fahrenheit_" android:id="#+id/textView1"
android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<EditText android:text="" android:id="#+id/txtFahrenheit" android:layout_width="match_parent"
android:layout_height="wrap_content"></EditText>
<TextView android:text="#string/kelvin_" android:id="#+id/textView1"
android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
<EditText android:text="" android:id="#+id/txtKelvin" android:layout_width="match_parent"
android:layout_height="wrap_content"></EditText>
</LinearLayout>
Fragment class:
public class TempConverterFragment extends Fragment {
EditText txtCelsius, txtFahrenheit, txtKelvin;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.temp_converter_fragment, container, false);
txtCelsius = (EditText) rootView.findViewById(R.id.txtCelsius);
txtFahrenheit = (EditText) rootView.findViewById(R.id.txtFahrenheit);
txtKelvin = (EditText) rootView.findViewById(R.id.txtKelvin);
...
}
...
}
Thanks in advance.
If SettingsActivity is not the Activity holding the FragmentPagerAdapter, then you would have to re-create all the fragments. The nature of a fragment is to be closely tied to it's activity.
If SettingsActivity is the Activity holding the FragmentPagerAdapter, then As I recall, FragmentPagerAdapter will initialize all the 8 fragments as soon as possible to have them ready when you swipe, unlike FragmentStatePagerAdapter. This means that you should (I think) be able to create each fragment in the constructor TabsPagerAdapter and keeping a reference to them, which you could access using getter methods on the TabsPagerAdapter.
Here is an example of how to get easy access to your pageradapter fragments:
public class DisplayPagerAdapter extends FragmentStatePagerAdapter {
private static final String TAG = "DisplayPagerAdapter";
SparseArray<DisplayFragment> registeredFragments = new SparseArray<DisplayFragment>();
#Inject DisplayCoreModule display;
public DisplayPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return (display != null && display.getPagesCount() > 0) ? display.getPagesCount() : 1;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
#Override
public Fragment getItem(int position) {
Log.d(TAG, "getItem " + position);
return DisplayFragment.newInstance(position);
}
#Override
public CharSequence getPageTitle(int position) {
if (display != null && display.getPagesCount() > 0) {
return "Side " + (position+1);
} else {
return super.getPageTitle(position);
}
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Log.d(TAG, "instantiateItem " + position);
DisplayFragment fragment = (DisplayFragment) super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
Log.d(TAG, "destroyItem " + position);
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
public SparseArray<DisplayFragment> getRegisteredFragments() {
return registeredFragments;
}
}
Now if you implement this usage of registeredFragments , you can call tabsPagerAdapter.getRegisteredFragment(2) to get your TempConverterFragment.
SparseArray<DisplayFragment> should be SparseArray<Fragment> in your case
Now this does not solve the your SettingsActivity problem. But if I understand you correctly, then adding the fragments your want directly in the layout XML of SettingsActivity would make sense. Then it would be easy to temporarily hide the fragments or whatever using:
FragmentManager fm=getFragmentManager();
Fragment myFragment=fm.findFragmentById(R.id.frag_tempconverter)
fm.beginTransaction().hide(myFragment).commit();
Notice the use of findFragmentById. The tag is usually used for dynamically added fragments (atleast in my mind). The findFragmentById will surely return a fragment if it is defined in the XML layout but just to be clear, it will be a new instance of the fragment.
To address your questions:
What if I move the fragments to the main activity XML? Won't it make things simpler
Do not think so, the updated answer shows how to easily access the fragments (from within your main activity).
Though not sure I can use FragmentManager in SettingsActivity
Sure you can. You can add new fragments, access available fragments (from predefined XML using findById or dynamically added using findByTag). You cannot, however, access the same instance of the fragment as was kept by your main activity.
To share information between the fragments and the two activities, you need to persist the state of your fragments somehow (which is a different topic).
All in all I think you are on the right path, you just need to combine the right pieces of the puzzle :)