So I'm working with my android app that takes data from firebase, basically a list of cars that I save into an ArrayList of a specific class. I want to show a list of CardView with one car object each, in other words, that each card displays the info about that car (model, km, price, etc). I know that the way I should do this is using a ListView in my layout, but I cant get the adapter to work.
The idea is that it shows something like this, but with car photos and stuff:
enter image description here
I'm not very familiarized with this type of structure since I started working on it recently, but I think I'm understand how it works. I made the cardview layout model to display each car and tried to make the ViewHolder class. Thanks in advice! My code:
Vehicle class:
public class Vehicle {
int anio, cv, kms, puertas;
double precio;
String combustible, foto, marca, modelo, palanca;
public Vehicle() {
} //setters and getters are too, but I omitted them
My cardview_template.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:padding="16dp"
>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/cv"
>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/car_photo"
android:src="#drawable/ic_home_icon"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginRight="16dp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/marca"
android:text="Mercedes-Benz"
android:layout_toRightOf="#+id/car_photo"
android:layout_alignParentTop="true"
android:textSize="30sp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/kms"
android:text="128000"
android:layout_toRightOf="#+id/car_photo"
android:layout_below="#+id/marca"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/precio"
android:text="2018"
android:layout_toRightOf="#+id/car_photo"
android:layout_below="#+id/kms"
/>
</RelativeLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
Part of the code that's in the method onCreate() in mi MainActivity:
I EDITED THE CODE. SEE IN THE EDIT BELOW
And finally, the class I used for the adapter. I think the problem is here:
I EDITED THE CLASS. SEE IN THE EDIT BELOW
EDIT
So following the advices, I changed the ListView with a RecyclerView, but it didn't work neither. I do not get any errors: CardViews simply do not appear in my RecyclerView.
Here's my new adapter class and the new code in the MainActivity onCreate()
Adapter:
public class VehicleListAdapter extends RecyclerView.Adapter<VehicleListAdapter.VehicleViewHolder> {
private ArrayList<Vehicle> mVehicleList;
public static class VehicleViewHolder extends RecyclerView.ViewHolder {
public ImageView mImageView;
public TextView mTextView1;
public TextView mTextView2;
public TextView mTextView3;
public VehicleViewHolder(View itemView) {
super(itemView);
mImageView = itemView.findViewById(R.id.car_photo);
mTextView1 = itemView.findViewById(R.id.marca);
mTextView2 = itemView.findViewById(R.id.modelo);
mTextView3 = itemView.findViewById(R.id.precio);
}
}
public VehicleListAdapter(ArrayList<Vehicle> vehicleslist) {
mVehicleList = vehicleslist;
}
#NonNull
#Override
public VehicleViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
//Links the cardview layout and prepares the ViewHolder
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.cardview_template, parent, false);
VehicleViewHolder vvh = new VehicleViewHolder(v);
return vvh;
}
#Override
public void onBindViewHolder(#NonNull VehicleViewHolder holder, int position) {
/*
* Links the layout elements with the variables in this class
* and put the object values in them
*/
Vehicle currentVehicle = mVehicleList.get(position);
//Uses Picasso librarie to set the image in the holder's ImageView
Picasso.get().load(currentVehicle.foto).into(holder.mImageView);
holder.mTextView1.setText(currentVehicle.getMarca());
holder.mTextView2.setText(currentVehicle.getModelo());
holder.mTextView3.setText(String.valueOf(currentVehicle.getPrecio()));
}
#Override
public int getItemCount() {
return mVehicleList.size();
}
MainActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);
toggle.syncState();
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
(...)
//Prepares the recycler view and shows the data
mRecyclerView = findViewById(R.id.recyclerView);
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(this);
mAdapter = new VehicleListAdapter(vehiclesList);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(mAdapter);
}
EDIT2
Ok so I've come across a very strange thing. If I put a debug point anywhere in the onCreate() and then execute the app debugging, it works just fine. I'm completely lost.
If I don't do this, it shows like this:
It works both in the emulator and my actual phone.
first of all you should use recyclerView instead of ListView.
here is what you missed:
in the onCreate method replace this:
VehicleListAdapter adapter = new VehicleListAdapter(this, R.layout.content_main, vehiclesList);
with
VehicleListAdapter adapter = new VehicleListAdapter(this, R.layout.cardview_template, vehiclesList);
I know that the way I should do this is using a ListView in my layout, but I cant get the adapter to work.
Actually you need to know that Android apps are given with certain amount of RAM to work with, which will be less if you consider handling images. Hence using ListView is not suggested for Image Handling, specially for huge number of images. The solution to this is using RecyclerView, instead of ListView.
Also if you need to use RecyclerView, you need to extend RecyclerViewAdapter for the Adapter Class, which will have its own ViewHolder, subclass.
RecyclerView is easy to use, but difficult initially. After practising you will get the hang of it.
You can follow this youtube link RecyclerView + CardView - Part 1 - LAYOUTS AND CUSTOM OBJECTS - Android Studio Tutorial
If you still have some queries please comment, I would be happy to answer.
Related
I wanted to show the part of next and previous items of the recyclerview as compared to the currently visible item (as in this third party library). However I managed to do so with my native recyclerview.
That's sound great to me, now I want to set the height of my currently visible item larger than the next and previous items to it as shown in the gif of the above mentioned library!
I have managed to show the next and previous items with my code as in the following stages:
1. Setting the recyclerview adapter as:
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
locationTypeFragmentBinding.mRecylerview.setLayoutManager(layoutManager);
locationTypeFragmentBinding.mRecylerview.setHasFixedSize(true);
final LocationTypePictureAdapter locationTypePictureAdapter =
new LocationTypePictureAdapter(getActivity(), locationDetails,false);
final SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(locationTypeFragmentBinding.mRecylerview);
locationTypeFragmentBinding.mRecylerview.post(new Runnable() {
#Override
public void run() {
locationTypeFragmentBinding.mRecylerview.
setAdapter(locationTypePictureAdapter);
}
});
2. My RecyclerView in the xml is:
<android.support.v7.widget.RecyclerView
android:id="#+id/mRecylerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/buttonsHolder"
android:clipToPadding="false"
android:orientation="horizontal"
android:padding="#dimen/_10sdp"
android:paddingStart="#dimen/_15sdp"
android:paddingEnd="#dimen/_15sdp"
android:scrollbarStyle="outsideOverlay"
android:visibility="gone"/>
3. My RecyclerView item xml is:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mainContainer"
android:layout_width="match_parent"
android:layout_height="#dimen/_200sdp">
<android.support.v7.widget.CardView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="#dimen/_5sdp"
android:layout_marginEnd="#dimen/_5sdp"
android:background="#android:color/transparent"
app:cardElevation="0dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/locationPicture"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
</android.support.v7.widget.CardView>
</RelativeLayout>
4. What I need to achieve is like:
I know I can use the above mentioned library, but I want to set the height of the currently visible item larger than the next and previous items of my recyclerview which are to be shown to the user.
Can somebody please figure out what I am doing wrong with my code?
Example Image of what I need
See Here
I am not familiar the library that you are using but You can also use CarouselLayoutManager to achieve this look, with this library you will have one elevated item that will be above all others and this will look bigger than the rest of the items.
How to use:
In your gradle:
implementation 'com.azoft.carousellayoutmanager:carousel:version'
When declaring an adapter:
final CarouselLayoutManager layoutManager = new CarouselLayoutManager(CarouselLayoutManager.VERTICAL);
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
Edit:
If you want to do it natively you can do something like this:
Create recyclerView:
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fadingEdge="horizontal"
android:overScrollMode="never"
android:requiresFadingEdge="horizontal" />
Attach your recyclerView to SnapHelper like this:
LinearSnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);
Now you need to provide the logic for your currently centered item:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
float position = (float) recyclerView.computeHorizontalScrollOffset() / (float) itemHeight;
int itemPosition = (int) Math.floor(position);
}
super.onScrollStateChanged(recyclerView, newState);
}
});
I had added animation to items of a recycleView using the below method. I hope it may solve your problem if you are able to do something similar to this
Try this changes in your onBindViewHolder of your recycleView adapter.
#Override
public void onBindViewHolder(#NonNull final CustomViewHolder holder, int position)
{
// do your work
setAnimation(holder.itemView, position);
}
and in setAnimation which receives the view and its position
void setAnimation(View view, int position) {
//this allows new views coming in the recycle view to slide to left while scrolling down and slide to right while scrolling up.
if (lastPosition < position) {
Animation animation = AnimationUtils.loadAnimation(context, android.R.anim.slide_in_left);
view.startAnimation(animation);
} else {
Animation animation = AnimationUtils.loadAnimation(context, R.anim.slide_in_right);
view.startAnimation(animation);
}
lastPosition = position;
}
Similarly, if you know the position or location of view which is in middle, you can use your anim to zoom it up or zoom down other views in the recycleView.
If you cary about performance it is the best to do all animations things in Layout Manager. Tamir Abutbul gives you very good solution, why you want to reinvent the wheel? Without library there is ready layout manager you need : https://stackoverflow.com/a/41307581/2551094
I'm looking for solution to my Listview where I would like to change the font color to red and size to any desired size of listview items.
I've tried using adaper but I'm countering multiple error.
Here is the xml file
<?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">
<android.support.v7.widget.Toolbar
android:id="#+id/my_toolbar"
android:layout_width="match_parent"
app:title="Space Time Alarm"
android:layout_height="56dp"
android:background="#30e410"
android:elevation="4dp"
android:theme="#style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"/>
<ListView
android:id="#+id/listView_Alarms"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="?android:attr/actionBarSize"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
</android.support.constraint.ConstraintLayout>
and Mainactivity file is
public class MainActivity extends AppCompatActivity {
public static boolean havePermission;
private ListView listView_Alarms;
private FloatingActionButton button_NewAlarm;
private Toolbar toolbar;
private DatabaseManager databaseManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
Log.d("SPACESTOREALARM", "CREATING");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//setting up the toolbar and support
toolbar = (Toolbar) findViewById(R.id.my_toolbar);
setSupportActionBar(toolbar);
listView_Alarms = (ListView) findViewById(R.id.listView_Alarms);
SpaceTimeAlarmManager.getInstance().initializeSpaceTimeAlarmManager(this);
databaseManager = DatabaseManager.getInstance(this);
databaseManager.initialize(this, listView_Alarms);
listView_Alarms.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
SpaceTimeAlarm alarm = (SpaceTimeAlarm) listView_Alarms.getItemAtPosition(position);
createOrEditAlarm(alarm);
}
});
}
}
Here list view contains list of alarms but due to same color both of font and background..the list cannot be seen. So I need help to change font color and size of listview items of above.
Thanks in advance.
I would recommend to create a custom ListView.
Check this for creating a custom ListView
With a custom ListView you have got a lot of more options to customize it.
You can set an adapter and change the background color and item size like below:
lv = findViewById(R.id.listView);
lv.setAdaptater(adapter);
final ArrayAdapter<String> adapter = new ArrayAdapter<String> (this, android.R.layout.simple_list_item_1, list) {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Get the current item from ListView
View view = super.getView(position, convertView, parent);
// Set the text color of TextView (ListView Item)
TextView tv = view.findViewById(android.R.id.text1);
// Generate ListView Item using TextView
tv.setTextColor(Color.parseColor("#efefef"));
tv.setTextSize(20);
tv.setAllCaps(true);
// Set a background color for ListView
view.setBackgroundColor(Color.parseColor("#00afd8"));
return view;
}
};
lv.setAdapter(adapter);
You can even alternate background colors.
I'm trying to hide certain elements from my custom toolbar which I've deployed in the MainActivity (it is the only activity in my app).
Accordingly, BotomNavigation has been implemented which handles 3 fragments namely Home, Records and Statistics.
The main toolbar.xml is composed as follows:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/colorPrimary"
android:elevation="4dp">
<TextView
android:id="#+id/toolbarTitle"
style="#style/TextAppearance.AppCompat.Widget.ActionBar.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar" />
<ImageView
android:id="#+id/profileImage"
android:layout_width="25dp"
android:layout_height="25dp"
android:src="#drawable/ic_account_circle_white_50dp"/>
As you can clearly see, it has
TextView, which dynamically displays and centers the name of the fragment the current user is on and
an ImageView which shows the authenticated user's image wherein the src is just a placeholder if the user doesn't have a profile picture.
Not everything works as expected using the following MainActivity.java :
public class MainActivity extends AppCompatActivity {
private TextView mTitle;
private String mUsername, mEmail;
private Uri mProfileImage;
private ImageView mProfileImageView;
private android.support.v7.widget.Toolbar mToolbar;
private static final String TAG = "BottomNavigation";
private BottomNavigationView.OnNavigationItemSelectedListener navListener;
public BottomNavigation() {
navListener = new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment fragment;
switch (item.getItemId()) {
case R.id.nav_home:
mTitle.setText("Home");
mToolbar.getMenu().findItem(R.id.profileImage).setVisible(false);
fragment = new HomeFragment();
loadFragment(fragment);
break;
case R.id.nav_records:
mTitle.setText("Records");
mToolbar.getMenu().findItem(R.id.profileImage).setVisible(false);
fragment = new RecordsFragment();
loadFragment(fragment);
break;
case R.id.nav_stats:
mTitle.setText("Statistics");
fragment = new StatisticsFragment();
loadFragment(fragment);
Glide.with(getApplicationContext()).load(mProfileImage).into(mProfileImageView);
break;
}
return true;
}
};
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bottom_naviation);
//replacing the default actionbar with custom toolbar
mToolbar = findViewById(R.id.tool_bar);
setSupportActionBar(mToolbar);
mTitle = mToolbar.findViewById(R.id.toolbarTitle);
//disabling the output of the appname on the toolbar
getSupportActionBar().setDisplayShowTitleEnabled(false);
BottomNavigationView bottomNav = findViewById(R.id.bottom_navigation);
bottomNav.setOnNavigationItemSelectedListener(navListener);
//load the Home fragment by default
loadFragment(new HomeFragment());
mTitle.setText("Home");
FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser();
mProfileImage = currentUser.getPhotoUrl();
mProfileImageView = mToolbar.findViewById(R.id.profileImage);
}
private void loadFragment(Fragment fragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, fragment);
transaction.commit();
}
}
What I'm trying to implement is to hide the ImageView on the Home and Record fragment but Statistics.
To do this, I used the following in the respective case statements:
mToolbar.getMenu().findItem(R.id.profileImage).setVisible(false);
However, for some reason the ImageView remains as is. What's interesting is:
1. that the place holder image continues to remain until and unless I move to Statistics fragment and fetch the image.
2. once fetched, the image remains continue to live in the toolbar even if move out from the Statistics, possibly, it is not getting hidden in the first place.
3. if I rotate into landscape, the fetched image gets erased, and I'm yet again presented with the placeholder.
I've also tried hiding the visibility by setting visibility gone on the ImageView itself, but to no avail.
mProfileImageView.setVisibility(View.GONE);
How can I actually go about in implementing the right logic, should I implement the toolbar in each and every fragment, which is certainly not a healthy practice and defeats the purpose of modular fragments?
Following are some screenshots in order for a better understanding:
ImageView not getting hidden, shows placeholder
Is visible even in the Records fragment
placeholder replaced with the user's image (fetched using FirebaseUI Auth)
rotating into landscape switches back to Home fragment and placeholder is replaced with the fetched image
as soon as I switch into statistics, imageview is replaced with the intended image
Views inside toolbar is same like other views I tried changing visibility it works.
toolbar.findViewById(R.id.image).setVisibility(View.VISIBLE);
public class TestActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
toolbar.findViewById(R.id.image).setVisibility(View.GONE);
toolbar.postDelayed(new Runnable() {
#Override
public void run() {
toolbar.findViewById(R.id.image).setVisibility(View.VISIBLE);
}
}, 5000);
}
}
my toolbar xml
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/holo_blue_light"
android:contentInsetEnd="0dp"
android:contentInsetLeft="0dp"
android:contentInsetRight="0dp"
android:contentInsetStart="0dp"
app:contentInsetEnd="0dp"
app:contentInsetLeft="0dp"
app:contentInsetRight="0dp"
app:contentInsetStart="0dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginRight="10dp"
android:src="#drawable/ic_launcher_foreground" />
<TextView
android:id="#+id/appTitle"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:gravity="center"
android:text="Title"
android:textColor="#android:color/background_dark"
android:textSize="20sp" />
</RelativeLayout>
</android.support.v7.widget.Toolbar>
I'm developing on Amazon Fire TV.
Because it's a TV app(No touch), I need focusables inside row's layout to be able to navigate around.
I have a really simple Recyclerview with image, text, and a focusable. When I press up or down, it all scrolls and stuff correctly, but I noticed that when I navigate faster than scroll can keep up, it creates new viewholders (Off screen) and lags up the UI.
I have created an activity with Creation numbers on it. When I scroll slowly, the highest creation # is 10. But when I scroll fast, I get cards with creation number 60 in a second. This causes an enormous lag and the application drops a lot of frames. Is my approach totally wrong?
Use the code below to test this out.
/**
* Created by sylversphere on 15-04-15.
*/
public class LandfillActivity extends Activity{
private Context context;
private static int ticketNumber;
private static int getTicket(){
ticketNumber ++;
return ticketNumber;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
setContentView(R.layout.landfill_activity);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
GridLayoutManager glm = new GridLayoutManager(context, 2);
recyclerView.setLayoutManager(glm);
SickAdapter sickAdapter = new SickAdapter();
recyclerView.setAdapter(sickAdapter);
}
public class SickViewHolder extends RecyclerView.ViewHolder{
TextView ticketDisplayer;
public ImageView imageView;
public SickViewHolder(View itemView) {
super(itemView);
ticketDisplayer = (TextView) itemView.findViewById(R.id.ticketDisplayer);
imageView = (ImageView) itemView.findViewById(R.id.imageView);
itemView.findViewById(R.id.focus_glass).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
context.startActivity(new Intent(context, LouisVuittonActivity.class));
}
});
}
public void setTicket(int value){
ticketDisplayer.setText(""+value);
}
}
public class SickAdapter extends RecyclerView.Adapter<SickViewHolder>{
#Override
public SickViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
SickViewHolder svh = new SickViewHolder(getLayoutInflater().inflate(R.layout.one_row_element, null));
svh.setTicket(getTicket());
return svh;
}
#Override
public void onBindViewHolder(SickViewHolder holder, int position) {
String[] image_url_array = getResources().getStringArray(R.array.test_image_urls);
Picasso.with(context).load(image_url_array[position % image_url_array.length] ).fit().centerCrop().into(holder.imageView);
}
#Override
public int getItemCount() {
return 100000;
}
}
}
one_row_element.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:orientation="horizontal">
<ImageView
android:id="#+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="#mipmap/sick_view_row_bg" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"
android:layout_marginLeft="15dp"
android:orientation="horizontal">
<TextView
android:id="#+id/virusTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Creation #"
android:textColor="#fff"
android:textSize="40sp" />
<TextView
android:id="#+id/ticketDisplayer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1"
android:textColor="#fff"
android:textSize="40sp" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/focus_glass"
android:background="#drawable/subtle_focus_glass"
android:focusable="true"
android:focusableInTouchMode="true"/>
</FrameLayout>
</FrameLayout>
test_image_urls.xml (Urls not owned by me)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="test_image_urls"
formatted="false">
<item>http://farm4.static.flickr.com/3175/2737866473_7958dc8760.jpg</item>
<item>http://farm4.static.flickr.com/3276/2875184020_9944005d0d.jpg</item>
<item>http://farm3.static.flickr.com/2531/4094333885_e8462a8338.jpg</item>
<item>http://farm4.static.flickr.com/3289/2809605169_8efe2b8f27.jpg</item>
<item>http://2.bp.blogspot.com/_SrRTF97Kbfo/SUqT9y-qTVI/AAAAAAAABmg/saRXhruwS6M/s400/bARADEI.jpg</item>
<item>http://fortunaweb.com.ar/wp-content/uploads/2009/10/Caroline-Atkinson-FMI.jpg</item>
<item>http://farm4.static.flickr.com/3488/4051378654_238ca94313.jpg</item>
<item>http://farm4.static.flickr.com/3368/3198142470_6eb0be5f32.jpg</item>
<item>http://www.powercai.net/Photo/UploadPhotos/200503/20050307172201492.jpg</item>
<item>http://www.web07.cn/uploads/Photo/c101122/12Z3Y54RZ-22027.jpg</item>
<item>http://www.mitravel.com.tw/html/asia/2011/Palau-4/index_clip_image002_0000.jpg</item>
<item>http://news.xinhuanet.com/mil/2007-05/19/xinsrc_36205041914150623191153.jpg</item>
<item>http://ib.berkeley.edu/labs/koehl/images/hannah.jpg</item>
<item>http://down.tutu001.com/d/file/20110307/ef7937c2b70bfc2da539eea9df_560.jpg</item>
<item>http://farm3.static.flickr.com/2278/2300491905_5272f77e56.jpg</item>
<item>http://www.pic35.com/uploads/allimg/100526/1-100526224U1.jpg</item>
<item>http://img.99118.com/Big2/1024768/20101211/1700013.jpg</item>
<item>http://farm1.static.flickr.com/45/139488995_bd06578562.jpg</item>
</string-array>
</resources>
subtle_focus
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:drawable="#color/glass_focus"/>
<item android:drawable="#color/glass_normal"/>
</selector>
glass_normal is #9000
glass_focus is #0000
Try increasing the maximum number of recycled views in the pool:
recyclerView.getRecycledViewPool().setMaxRecycledViews(50);
50 is an arbitrary number, you can try higher or lower and see what happens.
RecyclerView tries to avoid re-using views that have transient state, so if views are invalidated quickly or are animating, they may not be re-used right away.
Similarly if you have many smaller views, you may end up with more on screen than the default pool size can handle (more common with grid like layouts).
Picasso is holding you up but the suggested solution to build your own mechanism is not the way to go.
Picasso has fallen behind in the last year or so and today there are far better alternatives in the form of Google Glide and Facebook Fresco which specifically released updates to work better with RecyclerView and have proved faster and more efficient in loading, caching and storing in many tests which are available online such as:
http://inthecheesefactory.com/blog/get-to-know-glide-recommended-by-google/en
https://code.facebook.com/posts/366199913563917/introducing-fresco-a-new-image-library-for-android/
I hope that helped.
Good luck.
As the commenters pointed out pending responses from Picasso might be holding you up. If that is the case, you can solve it by extending ImageView and overriding the following method. I think it is worth trying.
#Override
protected void onDetachedFromWindow() {
Picasso.with(context).cancelRequest(this);
super.onDetachedFromWindow();
}
Update:
Turns out this is not the correct way, anyone wanting to cancel requests should do so in the onViewRecycled() callback as pointed out in the comments below.
By digging deeper in Sam Judd's answer I forced recycler to recycle the views by implementing the followings in its adapter.
#Override
public boolean onFailedToRecycleView(#NonNull VH holder)
return true;
}
As you can see here:
Called by the RecyclerView if a ViewHolder created by this Adapter cannot be recycled due to its transient state. Upon receiving this callback, Adapter can clear the animation(s) that effect the View's transient state and return true so that the View can be recycled. Keep in mind that the View in question is already removed from the RecyclerView.
In some cases, it is acceptable to recycle a View although it has transient state. Most of the time, this is a case where the transient state will be cleared in onBindViewHolder(ViewHolder, int) call when View is rebound to a new position. For this reason, RecyclerView leaves the decision to the Adapter and uses the return value of this method to decide whether the View should be recycled or not.
Note that when all animations are created by RecyclerView.ItemAnimator, you should never receive this callback because RecyclerView keeps those Views as children until their animations are complete. This callback is useful when children of the item views create animations which may not be easy to implement using an RecyclerView.ItemAnimator.
You should never fix this issue by calling holder.itemView.setHasTransientState(false); unless you've previously called holder.itemView.setHasTransientState(true);. Each View.setHasTransientState(true) call must be matched by a View.setHasTransientState(false) call, otherwise, the state of the View may become inconsistent. You should always prefer to end or cancel animations that are triggering the transient state instead of handling it manually.
For anyone else looking for a quick hack,
Do this. This will delay selection until it's been inflated and selected.
I don't know how but it works.
Just return null on onFocusSearchFailed.
/**
* Created by sylversphere on 15-04-22.
*/
public class SomeGridLayoutManager extends GridLayoutManager{
private final Context context;
public SomeGridLayoutManager(Context context, int spanCount) {
super(context, spanCount);
this.context = context;
}
public SomeGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
super(context, spanCount, orientation, reverseLayout);
this.context = context;
}
#Override
public View onFocusSearchFailed(View focused, int focusDirection, RecyclerView.Recycler recycler, RecyclerView.State state) {
return null;
}
}
I have a problem with adding a listview as a header in my listview (go go redundancy). The code is working, however only the first item shows up in the header. The body looks fine.
listView = (ListView) findViewById(R.id.default_list_view);
header = (ListView) getLayoutInflater().inflate(R.layout.savings_overview_header_list, null, false);
HeaderAdapter hAdapter = new HeaderAdapter(getLayoutInflater());
hAdapter.addItem("1");
hAdapter.addItem("2");
hAdapter.addItem("3");
header.setAdapter(hAdapter);
for (Policy p : saving.getPolicies()) {
adapter.addItem(p);
}
listView.addHeaderView(header);
listView.setAdapter(adapter);
headerView:
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/default_list_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000"
android:divider="#drawable/list_divider"
android:dividerHeight="2px"
android:fadingEdge="none"
android:fitsSystemWindows="true"
android:footerDividersEnabled="false"
android:headerDividersEnabled="false"
android:listSelector="#drawable/list_item_background_selected"
>
</ListView>
I have tried wrapping the ListView in a vertical LinearLayout but no cigar there either :(
hAdapter
private static class HeaderAdapter extends BaseAdapter {
private final LayoutInflater inflater;
protected ArrayList<String> data = new ArrayList<String>();
public HeaderAdapter(LayoutInflater inflater) {
this.inflater = inflater;
}
public void addItem(String s) {
data.add(s);
}
public View getView(int position, View convertView, ViewGroup parent) {
final PolicyViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.savings_overview_header_item, parent, false);
holder = new PolicyViewHolder();
holder.header = (TextView) convertView.findViewById(R.id.list_item_header);
holder.subHeader = (TextView) convertView.findViewById(R.id.list_item_subheader);
holder.img = (ImageView) convertView.findViewById(R.id.list_item_img);
convertView.setTag(holder);
} else {
holder = (PolicyViewHolder) convertView.getTag();
}
String s = data.get(position);
holder.header.setText(s);
holder.subHeader.setText(s);
holder.img.setImageResId(R.drawable.test);
return convertView;
}
public int getCount() {
return data.size();
}
public Object getItem(int position) {
return data.get(position);
}
public long getItemId(int position) {
return position;
}
private static class PolicyViewHolder {
TextView header;
TextView subHeader;
ImageView img;
}
}
header_item
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingRight="10dp" >
<ImageView
android:id="#+id/list_item_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:src="#drawable/ic_launcher"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:layout_marginLeft="5dp"
android:layout_toLeftOf="#+id/list_item_value"
android:layout_toRightOf="#+id/header_image"
android:orientation="vertical" >
<TextView
android:id="#+id/list_item_header"
style="#style/header_list_header"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/list_item_subheader"
style="#style/list_header_sub_header"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="sub header" />
</LinearLayout>
</RelativeLayout>
i have tried to wrap a linearlayout="vertical" around this one as well
You have to tell the parent view how to stack your elememnts. This is necessary for parents like a LinearLayout. If you're going to have such an adapter, you need to have a parent to contain these things, UNLESS its a single view.
Update
Whenever you want to do processing with the views in a ListView you
need to create a custom adapter that will handle your logic
implementation and pass that information to the views as necessary.
A custom adater would inflate the views piece by piece, this can be
dynamic of fixed.
I suspect that your problem relates to Android simply not knowing how to handle the two levels of "scrollability" this design implies. Think about it - If the user drags over the header, how is Android supposed to know whether they mean to scroll the outer list or the list within the header? I once recall Romain Guy making the same point about placing ListViews inside ScrollViews, it's slightly meaningless.
What's probably happening is that Android makes the header big enough to simply display a single header list item and it assumes any dragging gesture by the user should apply to the outer list.
Generally, I think what you've described sounds like a bad design choice and you need to take a step back.
What functionally do you want this UI to do?
If it's simply that you want to display two sets of content within a single list, then this is a problem you need to solve within the ListAdapter implementation you're using. e.g. Take a look Jeff Sharkey's blog post on his SeparatedListAdapter.
Make a LinearLayout with vertical orientation and add both ListViews there. That should fix your hiccup.
Cheers.