I am working on an app where there is a list of item and each item consist of a viewpager and below the viewpager you have other widgets (text, button....etc). I have to load url of images from the server and show the images inside my viewpager
Here is my Viewpager adapter:
public class ImageAdapter extends PagerAdapter {
private Context mContext;
private ArrayList<String> imagePaths;
private static final String TAG = ImageAdapter.class.getName();
// constructor
public FullScreenImageAdapter(Context context, ArrayList<String> imagePaths){
this.mContext = context;
this.imagePaths = imagePaths;
}
#Override
public int getCount() {
return null == imagePaths ? 0 : this.imagePaths.size();
}
#Override
public Object instantiateItem(final ViewGroup container, final int position) {
final LayoutInflater inflater = LayoutInflater.from(mContext);
final View viewLayout = inflater.inflate(R.layout.full_screen_image, container, false);
final ImageView image = (ImageView) viewLayout.findViewById(R.id.image);
final ProgressBar progressBar = (ProgressBar)viewLayout.findViewById(R.id.pBar);
String url = BuildConfig.URL_API_IMAGES;
final SparseArray<Object> mBitmapMap = new SparseArray<>();
Glide.with(mContext)
.load(url + imagePaths.get(position)).asBitmap()
.placeholder(R.drawable.car)
.dontAnimate()
.into(new BitmapImageViewTarget(image){
#Override
public void onLoadStarted(Drawable placeholder){
super.onLoadStarted(placeholder);
image.setImageDrawable(placeholder);
}
#Override
public void onResourceReady(Bitmap resource,
GlideAnimation<? super Bitmap> glideAnimation){
super.onResourceReady(resource, glideAnimation);
mBitmapMap.put(position, resource);
progressBar.setVisibility(View.INVISIBLE);
image.setImageBitmap(resource);
}
#Override
public void onLoadFailed(Exception e, Drawable errorDrawable){
super.onLoadFailed(e, errorDrawable);
progressBar.setVisibility(View.INVISIBLE);
}
});
container.addView(viewLayout);
return viewLayout;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
try{
container.removeView((RelativeLayout) object);
Glide.clear(((View) object).findViewById(R.id.image));
unbindDrawables((View) object);
object = null;
}catch (Exception e) {
Log.w(TAG, "destroyItem: failed to destroy item and clear it's used resources", e);
}
}
protected void unbindDrawables(View view) {
if (view.getBackground() != null) {
view.getBackground().setCallback(null);
}
if (view instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
unbindDrawables(((ViewGroup) view).getChildAt(i));
}
((ViewGroup) view).removeAllViews();
}
}
#Override
public boolean isViewFromObject(View view, Object object){
return view == ((RelativeLayout) object);
}
}
Here is a portion of my recycler view item:
<RelativeLayout
android:id="#+id/rl_pictureLayout"
android:layout_width="match_parent"
android:layout_marginLeft="#dimen/dimen_5"
android:layout_marginRight="#dimen/dimen_5"
android:layout_marginTop="#dimen/dimen_5"
android:layout_height="#dimen/sliderLayoutHeight">
<ViewPager
android:id="#+id/vp_photos"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<me.relex.circleindicator.CircleIndicator
android:id="#+id/indicator"
android:layout_width="match_parent"
android:layout_marginBottom="50dp"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
/>
<ImageButton
android:id="#+id/ib_bookmark"
android:background="#color/transparent"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="#dimen/dimen_16"
android:layout_marginRight="#dimen/dimen_16"
android:layout_marginEnd="#dimen/dimen_16"
android:src="#drawable/ic_bookmark_border_white_24dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextView
android:id="#+id/tv_picture_counter"
android:textColor="#color/white"
android:textAllCaps="true"
android:fontFamily="sans-serif"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginLeft="#dimen/dimen_16"
android:layout_marginStart="#dimen/dimen_16"
android:layout_marginBottom="#dimen/dimen_24"
/>
<TextView
android:id="#+id/tv_damagedPhotos"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/white"
android:fontFamily="sans-serif"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginEnd="#dimen/dimen_16"
android:layout_marginRight="#dimen/dimen_16"
android:layout_marginBottom="#dimen/dimen_24"
android:background="#color/transparent"
android:drawableRight="#drawable/ic_photo_camera_red_24dp"
android:drawableEnd="#drawable/ic_photo_camera_red_24dp"
android:drawablePadding="#dimen/dimen_5"
/>
</RelativeLayout>
<RelativeLayout
android:id="#+id/rl_timerLayout"
android:layout_below="#id/rl_pictureLayout"
android:layout_marginTop="#dimen/dimen_5"
android:layout_alignLeft="#id/rl_pictureLayout"
android:layout_alignStart="#id/rl_pictureLayout"
android:layout_alignRight="#id/rl_pictureLayout"
android:layout_alignEnd="#id/rl_pictureLayout"
android:layout_marginBottom="#dimen/dimen_10"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/tv_timer"
style="#style/text_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:textColor="#color/ColorBlack"
android:textStyle="bold"
/>
<TextView
android:id="#+id/tv_highest_bid"
style="#style/text_item"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
/>
<Button
android:id="#+id/btn_bid"
style="#style/button"
android:layout_width="60dp"
android:layout_height="30dp"
android:textAllCaps="false"
android:background="#drawable/curved_border_bgd_red"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:stateListAnimator="#null"
android:text="#string/btn_text_bid"
/>
</RelativeLayout>
Here is my (partial) viewholder class inside the recyclerview adapter
static class ViewHolder extends RecyclerView.ViewHolder
{
#BindView(R.id.indicator)
CircularIndicator mCircleIndicator;
#BindView(R.id.vp_photos)
ViewPager mViewPagerPhotoView;
public ViewHolder(View itemView)
{
super(itemView);
ButterKnife.bind(this, itemView);
}
}
In onBindViewHolder this is what i've got (as far as the viewpager is concerned):
Item item = mDataSet.get(position);
mImageAdapter = new ImageAdapter(mContext, item.getUrlPhotos());
holder.mViewPagerPhotoView.setAdapter(mImageAdapter);
holder.mViewPagerPhotoView.setOffscreenPageLimit(mImageAdapter.getCount() - 1);
holder.mCircleIndicator.setViewPager(holder.mViewPagerPhotoView);
Right now my reclerview has 10 items that I load from the server. Thing is, I noticed that when I scroll down say to the 7th item of my list, everything seems fine but when I scroll up back to see say the 1st item of my recycler list, the images found in viewpager of the 1st item tend to disappear and are (temporarily) replaced by the placeholder image i.e the viewpager is completely emptied and I temporarily have the placeholder; if the item is still visible on my screen the images for the viewpager a loaded again from the server.. I picked Glide because it's well known for it's caching capabilities. Moreover, a viewpager can have upto 20 IMAGES
!!...
Does anyone have an idea on how to solve this ??
Am I doing anything wrong ??
Any help will be appreciated.....
The "problem" is that the items of the recyclerview will be recycled. You never save the loaded images. If you scroll down and up, onBindViewHolder will be called and a new adapter instance will be created so the images will be loaded again and again on every call of onBindViewHolder.
The solution is to save the adapter object (in a map -> position, adapter) and bind it inside of onBindViewHolder.
ImageAdapter adapter = adaptermap.get(postion);
int adapterPosition = 0;
if(adapter == null) {
adapter = new ImageAdapter(mContext, item.getUrlPhotos());
adaptermap.put(position, adapter);
adapterPosition = viewPageStates.get(position);
}
holder.mViewPagerPhotoView.setAdapter(adapter);
holder.mViewPagerPhotoView.setCurrentItem(adapterPosition);
You also have to save the position of the viewpager.
Put this as a member:
// you also can use a SparseIntArray for better performance
// if the itemcount is less than a few hundret
public HashMap<Integer, Integer> viewPageStates = new HashMap<>();
Also add this in your recyclerview adapter:
#Override
public void onViewRecycled(YourViewHolderClass holder) {
int position = holder.getAdapterPosition();
viewPageStates.put(position, holder.mViewPagerPhotoView.getCurrentItem());
super.onViewRecycled(holder);
}
Important:
My solution will only work if you don't add / remove / sort items because we save adapter positions. If you also need it you have to modify the maps on add / delete / sort etc
Well the problem is about image. You said "a viewpager can have upto 20 IMAGES", I don't know what do you mean by upto 20. ViewPager design to be unlimited item unless you load all item at one which doesn't make sense.
So as you describe, I guess the problem is your image is being recycle that why when you scroll up you won't see your image again until Glide load the image and render it again.
So the solution is not quite simple to deal but first let me explain about image and viewpager. Let said you have 10 items in recyclerview adapter and recyclerview loaded only 5 items as necessary. So now the minimum total image that need to be in the memory is 5 x 2 (Each viewpager need at least 2 images) equal 10 images in total. This is huge and it depend on your image, if your image is quite large you will run out of memory and that when Glide will destroy some image to make space for the new coming. Hope you can see the impact of image being loaded.
Now my suggestion:
Make sure your Glide has diskcache configuration, this way your image will not loaded from the server one it loaded as long as the diskcache still has available space.
Shrink your image to fit your view. Example original image is 1440x320 and your view is only 1080x240 then it better to load 1080x240 into memory. Or you can even make it a little smaller than 1080x240.
Let Glide control the cache with builder.setMemoryCache(new LruResourceCache(yourSizeInBytes))
You can however make use of RecyclerView.ViewCacheExtension to manage your own view recycler but I don't see it would help. Moreover you can also implement cache view for Pager adapter, ViewPager deos not recycler any view so when ViewPager need a View it simply call instantiateItem and View will be created.
Try to keep ImageAdapter's instance in ViewHolder:
static class ViewHolder extends RecyclerView.ViewHolder
{
#BindView(R.id.indicator)
CircularIndicator mCircleIndicator;
#BindView(R.id.vp_photos)
ViewPager mViewPagerPhotoView;
ImageAdapter mAdapter;
public ViewHolder(View itemView)
{
super(itemView);
ButterKnife.bind(this, itemView);
mAdapter = new ImageAdapter();
mViewPagerPhotoView.setAdapter(mAdapter);
}
//Add bind() method which will be called in onBindViewHolder()
public void bind(ArrayList<String> imagePaths) {
//... maybe some other bindings
mAdapter.init(imagePaths);
mViewPagerPhotoView.setOffscreenPageLimit(mAdapter.getCount() - 1);
mCircleIndicator.setViewPager(mViewPagerPhotoView);
}
}
Create init(ArrayList<String> imagePaths) method in your adapter:
void init(ArrayList<String> paths) {
this.imagePaths = paths;
notifyDataSetChanged();
}
It should help. If it didn't helped then something is wrong in your using of Glide.
Related
My issue:
In my xml file, I define android:visibility="gone" in the linear layout labelled as assess_layout_list. Then, in the onClick() of course_adapter_layout, the whole view, I set the visibility back to View.VISIBLE, which does not work, even though the Log call just before it works, the LinearLayout object called assess_list_layout is not null, and it does work when I define the visibility="invisible" in the xml file. I want it to be gone at first though, and visible after clicking as this fits the design of the app.
Here is my course_adapter_view.xml file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/course_adapter_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="left"
>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginTop="20dp"
android:padding="15dp"
android:elevation="2dp"
android:background="#drawable/course_header_background">
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:src="#drawable/course_color_circle"/>
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.25"/>
<TextView
android:id="#+id/course_adapter_course_code"
android:text="TextView1"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_weight="0.5"/>
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.25"/>
<TextView
android:id="#+id/course_adapter_course_title"
android:text="TextView2"
android:layout_width="wrap_content"
android:layout_height="20dp"/>
</LinearLayout>
<LinearLayout
android:id="#+id/assess_list_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginRight="40dp"
android:layout_marginLeft="40dp"
android:background="#drawable/course_body_background"
android:padding="20dp"
android:visibility="gone"
>
<ListView
android:id="#+id/course_adapter_assess_list"
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_marginBottom="10dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:text="More" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:text="New"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
Here is my CourseListAdapter.java file that I use to create each view for each course in the list of courses, minus the usual stuff:
package com.example.schoolplanner2.adapters;
public class CourseListAdapter extends ArrayAdapter<Course> {
private static final String TAG = "CourseListAdapter";
private Context context;
int mResource;
public CourseListAdapter(#NonNull Context context, int resource, #NonNull ArrayList<Course> objects) {
super(context, resource, objects);
this.context = context;
mResource = resource;
}
#NonNull
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
// get info
String course_code = getItem(position).getCourseCode();
Double course_grade = getItem(position).getCurrentGrade();
// make inflater and inflate the layout
LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(mResource, parent, false);
TextView tv_course_code = v.findViewById(R.id.course_adapter_course_code);
TextView tv_course_title = v.findViewById(R.id.course_adapter_course_title);
tv_course_code.setText(course_code);
tv_course_title.setText(String.valueOf(course_grade));
// add on click to each list view element
LinearLayout layout = v.findViewById(R.id.course_adapter_layout);
layout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.i(TAG, "List view element has been clicked " + course_code);
// expand the view to include a new fragment
LinearLayout assess_list_layout = view.findViewById(R.id.assess_list_layout);
assess_list_layout.setVisibility(View.VISIBLE);
// get the list view and add each course to the course view
ListView assessment_list_view = (ListView) view.findViewById(R.id.course_adapter_assess_list);
AssessmentListAdapter assessAdapter = new AssessmentListAdapter(getContext(), R.layout.assessment_adapter_view, getItem(position).getAssessmentList(), getItem(position));
assessment_list_view.setAdapter(assessAdapter);
}
});
return v;
}
}
Please let me know if there is any more information you need. Will also take suggestions on other ways of accomplishing the same thing. Thanks for your help.
~Seth.
Edit: when the assess_list_layout.setVisibility(View.VISIBLE) is outside of the onClick it does work.
Further Edit: Things I have tried so far to no avail:
moving the location of where I define the LinearLayout componenent
calling invalidate() on parent view
using runOnUiThread()
changing view to v in the line where I attempt to findViewById for assess_list_layout, they are the same thing so it does not help.
calling requestLayout() on assess_list_layout
Update: I have now managed to get the assess_list_layout section to appear when the course_adapter_layout is clicked on. The only problem now is that the view does not take up anymore space on the screen, it just turns into a scrollable view that can be scrolled up and down on to see the whole view.
Also, when I scroll to fast, it resets the view back to the way it was on bootup.
1.View Visibility not working
The Visibility is not working because the view is not rendered initially. Remove the visibility gone in the xml and handle the visibility fully in the adapter class. In the 'assess_list_layout' linerlayout height can be hardcode because inside this layout the listview height is already hardcoded. You can hardcode to 300 and check. This way will help the view to get the initial rendering.
2. Scroll issue
While scrolling the already visible 'assess_list_layout' view might be not visible. This is because we need to handle the visibility, this handling is similar to checkbox selection handling in listview. Hope the Course class is model class, in that add a another property named isSelected as boolean and set the default value to false. Please refer below the course class,
Course Class
public class Course {
private boolean isSelected = false;
public boolean isSelected() {
return isSelected;
}
public void setSelected(boolean selected) {
isSelected = selected;
}
}
please refer the below code changes in the adapter class.
public class CourseListAdapter extends ArrayAdapter<Course> {
private static final String TAG = "CourseListAdapter";
private Context context;
int mResource;
public CourseListAdapter(#NonNull Context context, int resource, #NonNull ArrayList<Course> objects) {
super(context, resource, objects);
this.context = context;
mResource = resource;
}
#NonNull
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
// get info
String course_code = getItem(position).getCourseCode();
Double course_grade = getItem(position).getCurrentGrade();
// make inflater and inflate the layout
LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(mResource, parent, false);
TextView tv_course_code = v.findViewById(R.id.course_adapter_course_code);
TextView tv_course_title = v.findViewById(R.id.course_adapter_course_title);
//My Change
// expand the view to include a new fragment
LinearLayout assess_list_layout = view.findViewById(R.id.assess_list_layout);
// get the list view and add each course to the course view
ListView assessment_list_view = (ListView) view.findViewById(R.id.course_adapter_assess_list);
assess_list_layout.setVisibility(View.GONE);
if (getItem().get(position).isSelected()) {
assess_list_layout.setVisibility(View.Visible);
AssessmentListAdapter assessAdapter = new AssessmentListAdapter(getContext(), R.layout.assessment_adapter_view, getItem(position).getAssessmentList(), getItem(position));
assessment_list_view.setAdapter(assessAdapter);
}
//My Change
tv_course_code.setText(course_code);
tv_course_title.setText(String.valueOf(course_grade));
// add on click to each list view element
LinearLayout layout = v.findViewById(R.id.course_adapter_layout);
layout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.i(TAG, "List view element has been clicked " + course_code);
//My Change
getItem().get(position).setSelected(true);
//My Change
assess_list_layout.setVisibility(View.VISIBLE);
AssessmentListAdapter assessAdapter = new AssessmentListAdapter(getContext(), R.layout.assessment_adapter_view, getItem(position).getAssessmentList(), getItem(position));
assessment_list_view.setAdapter(assessAdapter);
}
});
return v;
}
}
I have commented as My Change to find the difference in the code.
issue 1
// make inflater and inflate the layout
LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(mResource, parent, false);
you don't reuse view,which leads to inflate a new view every time when called getView();As inflate() method is IO sensitive,it slow down the smoothness of scrolling ,trigger jank。
try this
// make inflater and inflate the layout
View v = null;
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from(context);
v= inflater.inflate(mResource, parent, false);
} else {
v = convertView;
}
issue 2
when scorll listview ,you need reset itemview state,add a "expand" property to your Course bean ,when click item set expand = true;and then add flowing code above layout.setOnClickListener
v.findViewById(R.id.assess_list_layout).setVisibility( item.expand ? View.VISIBLE:View.GONE);
ListView assessment_list_view = (ListView) v.findViewById(R.id.course_adapter_assess_list);
if (item.expand) {
AssessmentListAdapter assessAdapter = new AssessmentListAdapter(getContext(), R.layout.assessment_adapter_view,
item.getAssessmentList(), item);
assessment_list_view.setAdapter(assessAdapter);
}
issue 3
set setOnClickListener in getView() method , will create a new Clicker instance every time getView() called. use listView.setOnItemClickListener() instead
tips:
after all,you should use RecyclerView instead of ListView,which is a powerful UI widget
I'm currently working on a basic shopping cart project where I select items from a RecyclerView and add them to a custom ArrayList which should then be seen in EstimateActivity in a new Recycleview. I'm able to add custom objects to the "cartList" and pass it through intent(through parseable). When I click the button to go to the Estimate Activity all that appears in the new RecyclerView are an appropriate amount of CardViews without any of the TextViews(code and name). Am I wrong to believe that the intent is being passed correctly due to the fact that the correct amount of CardViews are found in the EstimateActivity's RecyclerView? Since everything else is working I am having troubles figuring out where the bug lies. Any help with how to figure out bugs like this on my own would also be appreciated.
Here is how I'm passing the intent from the Main Activity to Estimate Activity:
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, EstimateActivity.class);
intent.putExtra("cartList", cartList);
v.getContext().startActivity(intent);
}
});
This is the Estimate Activity:
public class EstimateActivity extends AppCompatActivity {
private RecyclerView eRecyclerview;
private CartAdapter eAdapter;
private RecyclerView.LayoutManager eLayoutManager;
private ArrayList<Inventory> eCartList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_estimate);
eCartList = new ArrayList<Inventory>();
Bundle bundle = getIntent().getExtras();
eCartList = bundle.getParcelableArrayList("cartList");
eRecyclerview = findViewById(R.id.recyclerview);
eLayoutManager = new LinearLayoutManager(this);
eAdapter = new CartAdapter(eCartList); // THIS IS WHERE IM PASSING THE LIST
eRecyclerview.setLayoutManager(eLayoutManager);
eRecyclerview.setAdapter(eAdapter);
}
}
Here is the CartAdapter:
public class CartAdapter extends RecyclerView.Adapter<CartAdapter.CartViewHolder> {
private ArrayList<Inventory> eInventoryList;
public static class CartViewHolder extends RecyclerView.ViewHolder {
private TextView eCardCode;
private TextView eCardName;
private CardView eContainerView;
public CartViewHolder(View itemView) {
super(itemView);
eCardCode = itemView.findViewById(R.id.code_view);
eCardName = itemView.findViewById(R.id.name_view);
eContainerView = itemView.findViewById(R.id.container_view2);
}
}
public CartAdapter(ArrayList<Inventory> eCartList){ //this is where i recieve the list
eInventoryList = eCartList;
}
#NonNull
#Override
public CartViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.text_row, parent, false);
CartViewHolder mvh = new CartViewHolder(view);
return mvh;
}
#Override
public void onBindViewHolder(#NonNull CartViewHolder holder, int position) {
Inventory currentItem = eInventoryList.get(position);
holder.eCardName.setText(currentItem.getName());
holder.eCardCode.setText(currentItem.getCode());
holder.eContainerView.setTag(currentItem);
}
#Override
public int getItemCount() {
return eInventoryList.size();
}
}
Lastly here is the XML for the CardView in the RecyclerView
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="#color/black"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:cardCornerRadius="5dp"
android:padding="5dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:id="#+id/container_view2"
>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="4dp">
<TextView
android:id="#+id/code_view"
android:text="code_view"
android:textSize="10sp"
android:textColor="#color/white"
android:layout_centerInParent="true"
android:layout_alignParentTop="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"
/>
<TextView
android:id="#+id/name_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:layout_alignParentTop="true"
android:layout_marginTop="0dp"
android:text="Name Name Name Name Name"
android:textColor="#android:color/white"
android:textSize="20sp"
android:textStyle="bold"
android:paddingTop="10dp"
/>
</RelativeLayout>
</androidx.cardview.widget.CardView>
Try to debug the application, put a breakpoint after receiving the bundle content and check what values it has to make sure that it is arriving well, you can do the same inside the adapter.
This can be done every time you need to know what values the variables have at a specific time and place
Good luck in this new world !!
I am working on an Android application in which I have one container called as Section and there can be Note objects inside it. The use-case is that a user can put multiple notes in a section and organize them. Currently I am to display the section names retrieved from the server with a background image.
Now my problem is how can I display the multiple notes received from the server inside the section.
I understand that this can be achieved by FrameLayout, but a dynamic Note count is what my problem is.
Please note that the count of notes can vary, depending upon user.
Here is the original screenshot of how sections look currently :
Now when you would add notes, it ideally should look like this :
Each of those blocks inside the section contains Note objects. To display its contents, I want to show a note block kind of image and just few words
of the note contents.
Currently I have code to retrieve the Notes from the server, sections can be displayed, but I really have no idea how to proceed because notes can be dynamic. Here is my code so far.
public class GroupSectionActivity extends Activity {
private SectionServiceImpl sectionService = new SectionServiceImpl();
private NoteServiceImpl noteService = new NoteServiceImpl();
private static volatile List<RestSection> restSectionList = new ArrayList<>();
private static volatile List<RestNote> restNoteList = new ArrayList<>();
private static volatile Long groupAccountId;
private static volatile Integer canvasid;
ListView listView;
SectionLazyAdapter sectionLazyAdapter;
static final String msectionname = "msectionname";
static final String msectionid = "msectionid";
Button addSectionButton;
EditText sectionName;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sectionlayout);
Bundle extras = getIntent().getExtras();
if (extras != null) {
groupAccountId = extras.getLong("groupid");
canvasid = extras.getInt("canvasid");
}
restSectionList = this.sectionService.getSectionByCanvas(canvasid);
ArrayList<HashMap<String, String>> restSectionArrayList = new ArrayList<HashMap<String, String>>();
for (RestSection restSection : restSectionList) {
HashMap<String, String> sectionDisplay = new HashMap<>();
sectionDisplay.put("msectionid", String.valueOf(restSection.getMsectionid()));
sectionDisplay.put("msectionname", restSection.getMsectionname());
restSectionArrayList.add(sectionDisplay);
}
listView = (ListView) findViewById(R.id.seclist);
sectionLazyAdapter = new SectionLazyAdapter(this, restSectionArrayList);
listView.setAdapter(sectionLazyAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
int sectionId = restSectionList.get(position).getMsectionid();
Log.d("Sectionid is ", String.valueOf(sectionId));
/*Intent intent = new Intent(GroupSectionActivity.this, GroupSectionActivity.class);
intent.putExtra("groupid", groupAccountId);
intent.putExtra("sectionid", sectionId);
startActivity(intent);
finish();*/
}
});
BaseAdapter to manage the guys :
public class SectionLazyAdapter extends BaseAdapter{
private Activity activity;
private ArrayList<HashMap<String, String>> data;
private static LayoutInflater inflater=null;
public SectionLazyAdapter(Activity a, ArrayList<HashMap<String, String>> d) {
activity = a;
data=d;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public int getCount() {
return data.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
View vi=convertView;
if(convertView==null)
vi = inflater.inflate(R.layout.activity_group_section, null);
TextView sectionName = (TextView)vi.findViewById(R.id.sectionname); // title
// ImageView sectionImage=(ImageView)vi.findViewById(R.id.sectionimage); // thumb image
HashMap<String, String> sectionList = new HashMap<String, String>();
sectionList = data.get(position);
// Setting all values in listview
sectionName.setText(sectionList.get(GroupSectionActivity.msectionname));
return vi;
}
}
activity_group_section.xml :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="5dip" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:weightSum="1">
<ImageView
android:id="#+id/sectionimage"
android:layout_width="wrap_content"
android:layout_height="300dp"
android:scaleType="fitXY"
android:src="#drawable/sectionbackground"
/>
</FrameLayout>
<TextView
android:id="#+id/sectionname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/textView"
android:visibility="visible"
android:gravity="center"
android:layout_gravity="center_horizontal" />
</LinearLayout>
</RelativeLayout>
sectionlayout.xml :
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="{relativePackage}.${activityClass}" >
<ListView
android:id="#+id/seclist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="#+id/sectionAddButton">
</ListView>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/sectionAddButton"
android:layout_alignParentTop="true"
android:background="#drawable/sectionbackground"
android:text="Add Section" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/sectionNameTextField"
android:layout_alignBottom="#+id/sectionAddButton"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_toEndOf="#+id/sectionAddButton"
android:hint="Section Name"
android:gravity="center"
android:layout_toRightOf="#+id/sectionAddButton" />
</RelativeLayout>
I hope the question is clear, if there is anything missing, kindly let me know.
If you want to display the notes in a dynamic way, you should implement a GridView inside each container, if you set the right margin to each note inside the Grid, the component will dimension itself to fit your section.
The GridView adapter is really simple, works just like the ListView adapter, you will just need to define the number of columns, you can do this in the XML, or programmatically in your Java code.
<GridView
android:id="#+id/grid_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="3"/>
First let me point out that Thomaz is right and you should use a GridView.
It's the right way to go both for your needs and ease of use, but more importantly for it's ability to recycle it's views.
If you won't use any form of view recycling you might get out of memory exception.
But now you face another problem: you want it to be shown in sections.
Why is that a problem? Because:
A) Both the ListView and the GridView do recycling with their child views, and now that each child view of the ListView is a single GridView, which holds inside of it more Views, it's a pretty complex thing to manage. No impossible, but pretty complex.
B) Because of the fact that both the ListView and the GridView are scrollable (and because of that fact are recyclable) there is an issue of scrolling inside scrolling that needs to be resolved.
Luckily I cam across an answer: SuperSLiM (Formally StickyGridHeaders).
This should provide you with an easy solution which suites your needs.
Good luck.
I want to make a clickable listView with editable editText.
I have a custom list adapter.java, custom list item.xml, mainActivity.java.
I tried
1. android:descendantFocusability="blocksDescendants"
=> failed. can't edit editText.
2. editText android:focusable/enable/clickable = true
=> failed. can't click listView Item
3. getView{editText.onClickListener}
=>failed.
I want to EDITABLE editText, not just clickable editText & Clickable listView(listItem).
please help.
customitem.XML
...
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical"
android:descendantFocusability="blocksDescendants"
>
//android:descendantFocusability="blocksDescendants" doesn't work
<EditText
android:id="#+id/tvItem"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:textSize="20sp"
android:textColor="#ffffff"
android:layout_marginLeft="25dp"
android:textStyle="bold"
android:background="#null"
android:shadowColor="#color/shadow"
android:shadowDx="3"
android:shadowDy="3"
android:shadowRadius="1"
android:imeOptions="actionDone"
android:inputType="text"
android:focusable="true"
/>
<TextView
android:id="#+id/tvItemCount"
android:layout_width="#dimen/list_height"
android:layout_height="70dp"
android:layout_alignParentRight="true"
android:textColor="#ffffff"
android:textSize="22dp"
android:text="6"
android:background="#3300b7ff"
android:gravity="center_vertical|center_horizontal"
android:shadowColor="#color/shadow"
android:shadowDx="3"
android:shadowDy="3"
android:shadowRadius="1"
android:textStyle="bold"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:background="#color/dark_shadow" >
</LinearLayout>
</RelativeLayout>
...
After few attempts which I've made by myself, I've asked uncle Google about your problem. Do you know, what he said?
ListView aren't able to handle EditText view well. If many people couldn't resolve this issue before, maybe you will consider some 'workaround' like this desribed HERE. It is an answer to other issue, but probably even if you will fix above problem, you will meet this one.
In brief #Andrew recommends to use ScrollLayout with simple LinearLayout inside instead of ListView. In onCreate method he inflates the View used for list items and add it to LinearLayout, and store this in ArrayList as well, to save data to every view later.
I know it isn't solution for your problem, but maybe it let you a lot of time, which you will spend looking for any reasonable solution.
Edit
It is funny. Inspired by #Rishabh Srivastava link I've tried to find some solution (I know, I'm a little bit stubborn).
I've create adapter layout - RelativeLayout, which is fully filled by Button and above it (I mean literally above it - in Z axis) I've placed EditText view. I thought that edittext will handle click on it and button will handle clicks outside of edittext view. Unfortunately 'click' event propagate through all of views - so by clicking on edittext, we will click on button as well.
I thought I am smarter than everybody so I used OnTouchListener - we can handle single 'touch' event and return true value, as information to OS that we handle it.
And you know what? I've met problem exactly the same like desribed in above link:
When I click on an EditText, the virtual keyboard shows itself, but the EditText loses focus and I have to click the EditText again.
I hope you don't want lost your time any more;)
first of all, thank you everyone!
I tried all of the answer, but it didn't work...:(
Focusable EditText in the ListView and onItemClick
it works for me
my code ▼
public class subMyListAdapter extends BaseAdapter{
Context context;
LayoutInflater Inflater;
ArrayList<subMyItem> arraySrc;
int layout;
static int currentTheme = 0;
EditText tvItem;
RelativeLayout rl_inflate;
UserHolder holder;
public subMyListAdapter(Context context, int layout, ArrayList<subMyItem> arraySrc)
{
this.context = context;
this.layout = layout;
this.arraySrc = arraySrc;
Inflater = (LayoutInflater)context.getSystemService(context.LAYOUT_INFLATER_SERVICE);
}
public int getCount()
{
return arraySrc.size();
}
public String getItem(int position)
{
return arraySrc.get(position).list;
}
public long getItemId(int position)
{
return position;
}
public View getView(final int position, View conv, ViewGroup parent)
{
holder = null;
if (conv == null)
{
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
conv = inflater.inflate(layout, parent, false);
holder = new UserHolder();
holder.tvItem = (EditText)conv.findViewById(R.id.tvItem);
conv.setTag(holder);
}
else
{
holder = (UserHolder) conv.getTag();
}
if(holder == null)
{
holder = new UserHolder();
holder.tvItem = (EditText)conv.findViewById(R.id.tvItem);
conv.setTag(holder);
}
subMyItem user = arraySrc.get(position);
holder.tvItem.setOnTouchListener(test);
conv.setOnTouchListener(test);
if(conv == null)
{
conv = conv;
}
tvItem = (EditText) conv.findViewById(R.id.tvItem);
user = arraySrc.get(position);
tvItem.setText(user.list);
tvItem.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
Toast.makeText(context, "tvItem button Clicked",
Toast.LENGTH_LONG).show();
}
});
return conv;
}
View.OnTouchListener test= new View.OnTouchListener()
{
#Override
public boolean onTouch(View view, MotionEvent event)
{
if (view instanceof EditText)
{
EditText editText = (EditText) view;
editText.setFocusable(true);
editText.setFocusableInTouchMode(true);
} else
{
UserHolder holder = (UserHolder) view.getTag();
holder.tvItem.setFocusable(false);
holder.tvItem.setFocusableInTouchMode(false);
}
return false;
}
};
static class UserHolder
{
EditText tvItem;
}
}
What I am trying to accomplish is to have a checkbox in each row, having the ability to check the box separately (for batch deleting) and being able to select the entire row to view data associated with the list item.
I have done a checkbox with a textview but that only lets be select the box and I cant click on the list item to view that items data. I also used checkedTextView but that checks the box where ever you click on the row calling the onListItemClick and thats not what I want either. Is there some what I can separate checking the box from clicking a listview item?
Pretty much trying to do what the gmail app does when selecting messages to delete and view
this is my row layout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<CheckBox
android:id="#+id/checkBox1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"/>
<TextView
android:id="#+id/nameCheckTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="#+id/checkBox1"
android:layout_toRightOf="#+id/checkBox1"
android:paddingTop="5dp"
android:paddingLeft="15dp"
android:text="Name"
android:textAppearance="?android:attr/textAppearanceLarge" />
</RelativeLayout>
creating listview
#Override
public void onActivityCreated(Bundle state){
super.onActivityCreated(state);
lv = getListView();
lv.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
lv.setItemsCanFocus(false);
setEmptyText("No Bowlers");
registerForContextMenu(getListView());
populateList();
}
EDIT:
my populate method
public void populateList(){
String[] fields = new String[] {BowlersDB.NAME};
//mAdapter = new CheckAdapter(getActivity(),R.layout.check_listview,null,fields,new int[] {R.id.nameCheckTV});
mAdapter = new SimpleCursorAdapter(getActivity(),R.layout.check_listview,null,fields,
new int[] {R.id.nameCheckTV});
setListAdapter(mAdapter);
getLoaderManager().initLoader(0,null,this);
}
The issue is that Android doesn't allow you to select list items that have elements on them that are focusable. Try modifying the checkbox on the list item:
android:focusable="false"
I had a strange workaround with this issue. Here is my solution.
Use this for your ListView. Product is just a model object to hold your data in this case:
public class CatalogItemAdapter extends ArrayAdapter<Product> //
{
private ArrayList<Product> products;
private Activity activity;
public CatalogItemAdapter(Context context, int textViewResourceId,
ArrayList<Product> items, Activity activity) //
{
super(context, textViewResourceId, items);
this.products = items;
this.activity = activity;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) //
{
Product product = products.get(position);
if (convertView == null) //
{
LayoutInflater vi = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.catalog_item_stub, null, false);
}
}
}
Somewhere in your onResume(), put this:
listView = (ListView) activity.findViewById(R.id.CatalogProducts);
m_adapter = new CatalogItemAdapter(activity,
R.layout.catalog_item_stub, products, activity);
if (products == null)
products = new ArrayList<Product>();
listView.setAdapter(m_adapter);
R.layout.catalog_item_stub is a layout stub created in XML like so (put the appropriate items in it for you, like the checkbox):
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout android:id="#+id/catalog_item_stub"
android:layout_width="match_parent" android:layout_height="90dp"
xmlns:android="http://schemas.android.com/apk/res/android"
android:padding="5dp">
<LinearLayout android:layout_height="match_parent"
android:layout_width="match_parent" android:weightSum="5"
android:gravity="center_vertical|right">
<TextView android:layout_width="0dp" android:layout_height="match_parent"
android:text="product_title" android:textAppearance="?android:attr/textAppearanceMedium"
android:id="#+id/ProductTitle" android:padding="5dp"
android:layout_weight="2.5" android:textColor="#000000" />
<CheckBox android:padding="5dp" android:layout_height="match_parent"
android:text="select" android:layout_width="0dp" android:id="#+id/chkSelect"
android:layout_weight="1.5" android:textColor="#000000"
android:gravity="right" />
</LinearLayout>
Hopefully this helps! Holler if you need any clarification.
The gmail app uses its own view called CanvasConversationHeaderView that manages its subviews. This method is probably more heavy-weight than what you are looking for.
An easier method would be to make the checkbox not "focusable" (as Alex Lockwood suggests) and then attach an onClick in the XML.
<CheckBox
android:id="#+id/checkBox1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:focusable="false"
android:onClick="onCheckboxClicked"/>
Then in your activity code add
public void onCheckboxClicked(View view) {
RelativeLayout rl = (RelativeLayout)view.getParent();
Log.d(TAG, "Checkbox clicked! getTag returned: " + rl.getTag());
}
EDIT: How to add a tag from SimpleCursorAdapter.bindView
private class MyCursorAdapter extends SimpleCursorAdapter {
public MyCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
super(context, layout, c, from, to);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
//Log.d(TAG, "Cursor pos: " + cursor.getPosition());
String name = cursor.getString(
cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
view.setTag(name);
super.bindView(view, context, cursor);
}
}
Note: I set the tag to the View from the bindView call, which is the RelativeLayout at the root of your xml. Look at the onCheckboxClicked method to see how I got the tag.
You need to set an onItemClickListener for your ListView that will start another activity with the info of the row selected when the row is clicked (outside the CheckBox of course). I would recommend having your Activity implement AdapterView.OnItemClickListener which requires the method
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {}
Inside this method you can launch an Activity with details corresponding to the data in the row selected.
Hopefully I understood your question correctly.