editable EditText in clickable listView - java

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;
}
}

Related

How to recover view from View.gone. setVisibility(View.VISIBLE) not working after using 'android:visibility="gone"' in xml

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

Viewpager inside recyclerview adapter

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.

How to get selected text from ListView and Adapter in Android?

I have spent hours trying to figure this issue out and while I have come close, I am not getting the results I need. I have a messaging application that has a custom adapter and a listview with about 5 textviews. My issue is retrieving the text when a user long-presses on a message. After trying various methods, the closest I came was with:
mListView.setOnItemLongClickListener( new AdapterView.OnItemLongClickListener
(){
public boolean onItemLongClick(AdapterView<?> av, View v, int pos, long id) {
final String test = ((TextView)v).getText().toString();
Log.i(TAG, "Received: " + test);
return true;
}
});
The silly (read:frustrating) part about this code is that it works about 3-4 times successfully when clicked, however after that it force closes my application. The error I get is:
FATAL EXCEPTION: main
java.lang.ClassCastException: android.widget.RelativeLayout cannot be cast to android.widget.TextView
at android.widget.AbsListView.performLongPress(AbsListView.java:3347)
at android.widget.AbsListView$CheckForLongPress.run(AbsListView.java:3294)
at android.os.Handler.handleCallback(Handler.java:725)
The weird part is that I get the correct String of the message successfully the first few times, then it just doesn't want to work. After looking up the error, I tried cleaning the project multiple times, deleting the R.java file, and closing out Eclipse, but nothing has made a difference.
I have also tried using the position number of the messaging. The application never force closed when getting the position number of the message, however I couldn't get it to get the String that was attached to message at that location. The error was something about trying to convert from integer to string:
String test = ((EditText)av.getItemAtPosition(pos)).getText().toString();
My xml file consists of:
<?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"
android:gravity="center_horizontal"
android:orientation="horizontal" >
<LinearLayout
android:id="#+id/left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_margin="20dip"
android:background="#drawable/bubble_yellow"
android:gravity="left"
android:orientation="vertical"
android:visibility="invisible" >
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
android:paddingBottom="1dip"
android:paddingLeft="15dip"
android:paddingRight="20dip"
android:paddingTop="1dip"
android:singleLine="false"
android:text="Large Text"
android:longClickable="true"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="#+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:gravity="right"
android:paddingRight="20dip"
android:longClickable="true"
android:text="TextView" />
</LinearLayout>
<LinearLayout
android:id="#+id/right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_margin="20dip"
android:background="#drawable/bubble_green"
android:orientation="vertical"
android:visibility="invisible" >
<TextView
android:id="#+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="1dip"
android:paddingLeft="15dip"
android:paddingRight="20dip"
android:paddingTop="1dip"
android:singleLine="false"
android:longClickable="true"
android:text="Large Text"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="#+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:gravity="right"
android:longClickable="true"
android:paddingRight="20dip"
android:text="TextView" />
</LinearLayout>
</RelativeLayout>
Update 1: Added requested xml file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/background2"
android:orientation="vertical" >
<ListView
android:id="#+id/lv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:divider="#null"
android:dividerHeight="0dp"
android:longClickable="true"
android:stackFromBottom="true" >
</ListView>
<View
android:layout_width="match_parent"
android:layout_height="1dp" />
<LinearLayout
android:id="#+id/sendMsgLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginTop="20dp"
android:orientation="horizontal" >
<EditText
android:id="#+id/messageText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_weight="1"
android:hint="Type Message"
android:inputType="textCapSentences|textMultiLine"
android:textColor="#FFFFFF"
android:maxLines="3" >
<requestFocus />
</EditText>
<ImageView
android:id="#+id/imageSend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:src="#drawable/send512" />
</LinearLayout>
</LinearLayout>
Update2: My adapter contains the following:
ViewHolder viewHolder;
MessageModel array = data.get(position);
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = inflater.inflate(R.layout.message_item, null);
viewHolder.left =(LinearLayout)convertView.findViewById(R.id.left);
viewHolder.right =(LinearLayout)convertView.findViewById(R.id.right);
viewHolder.txt1 = (TextView) convertView
.findViewById(R.id.textView1);
viewHolder.txt3 = (TextView) convertView
.findViewById(R.id.textView3);
viewHolder.txt2 = (TextView) convertView
.findViewById(R.id.textView2);
viewHolder.txt4 = (TextView) convertView
.findViewById(R.id.textView4);
convertView.setTag(viewHolder);
Update 3: Tried my best to follow #MDragon00's advice, unfortunately my app is still crashing when long pressing. I now have:
mListView.setOnItemLongClickListener( new AdapterView.OnItemLongClickListener
(){
public boolean onItemLongClick(AdapterView<?> av, View v, int pos, long id) {
TextView textView1 = (TextView) v.findViewById(R.id.textView1);
TextView textView2 = (TextView) v.findViewById(R.id.textView2);
TextView textView3 = (TextView) v.findViewById(R.id.textView3);
TextView textView4 = (TextView) v.findViewById(R.id.textView4);
View.OnLongClickListener listener = new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
TextView textView = (TextView) v;
String text = textView.getText().toString();
Log.i(TAG, "Text Selected = " + text);
return false;
}
};
textView1.setOnLongClickListener(listener);
textView2.setOnLongClickListener(listener);
textView3.setOnLongClickListener(listener);
textView4.setOnLongClickListener(listener);
return true;
}
});
}
The description of what each parameter does in onItemLongClick: http://developer.android.com/reference/android/widget/AdapterView.OnItemLongClickListener.html
The issue here is that av is the entire ListView, while v is the entire row. Your row doesn't simply consist of a TextView, so you can't cast it to a TextView. In other words,
final String test = ((TextView)v).getText().toString();
isn't a good idea as the parent view is a RelativeLayout. A better solution would be to do the following:
TextView textView = (TextView) v.findViewById(R.id.idOfTextView);
String text = textView.getText().toString();
As a small side suggestion, it sounds like you want to simply click on the item, not long click it. Use an OnItemClickListener instead of a OnItemLongClickListener if it suits your needs.
Edit:
According to your comments, the user will click/tap on a certain TextView out of several TextViews within some abstract row, and you want to get one of those four TextViews.
To do so, you'll need a new listener on each TextView. I can't give an exact answer without seeing your adapter, but when you inflate/get the row view to give to the ListView in the adapter, set an OnClickListener to the TextViews. For example with some pseudo-code:
// Get the textViews, to assign a listener to them
TextView textView1 = (TextView) v.findViewById(R.id.textView1);
TextView textView2 = (Textview) v.findViewById(R.id.textView2);
..... // Etc and so on
// Create this listener anywhere, including via making the adapter/class
// implement the listener and passing it in instead
View.OnClickListener listener = new View.OnClickListener(
#Override
public void onClick(View v) {
// Get the view as a TextView, as this will only be used
// with TextViews, so the only view to be passed
// will be Textviews
TextView textView = (TextView) v;
// Do whatever with that text, or however you want to use the view
String text = textView.getText().toString();
someFunction(text);
}
);
textView1.setOnClickListener(listener);
textView2.setOnClickListener(listener);
..... // Etc and so on
Now then, if you need the exact row that was clicked AND the text from an exact TextView, then you'll need to use both of the listeners [ie, one on the ListView and ones on the TextViews] to get the appropriate data.
Edit Hopefully Final:
public class TestAdapter extends BaseAdapter {
Context mContext;
public TestAdapter (Context context, Data someData) {
this.mContext = context;
// Set up the data for your listView however
}
#Override
public int getCount() {
return howManyRows;
}
#Override
public Object getItem(int position) {
return objectAtPosition;
}
#Override
public long getItemId(int position) {
return 0;
} // Unnecessary, unless using databases
// The listener for the textViews, feel free to use different listeners
View.OnClickListener TextViewListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
// Get the view as a TextView, as this will only be used
// with TextViews, so the only view to be passed
// will be Textviews
TextView textView = (TextView) v;
// Do whatever with that text, or however you want to use the view
String text = textView.getText().toString();
someFunction(text);
}
};
// Using the efficient pattern for recycling the views rather than using
// findViewById repeatedly
public static class ViewHolder{
public TextView textView1;
public TextView textView2;
public TextView textView3;
public TextView textView4;
// And any other view that's part of the row view that you need
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = null;
ViewHolder holder;
if(convertView == null) {
// Then gotta set up this row for the first time
LayoutInflater inflater =
(LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = inflater.inflate(R.layout.row_layout.xml, parent, false);
// Create a ViewHolder to save all the different parts of the row
holder = new ViewHolder();
holder.textView1 = (TextView) row.findViewById(R.id.textView1);
holder.textView2 = (TextView) row.findViewById(R.id.textView2);
holder.textView3 = (TextView) row.findViewById(R.id.textView3);
holder.textView4 = (TextView) row.findViewById(R.id.textView4);
// Make the row reuse the ViewHolder
row.setTag(holder);
}
else { // Otherwise, use the recycled view
row = convertView;
holder = (ViewHolder) row.getTag();
}
// Set the current row's Textview onClickListeners
// Note: You MAY be able to only set these once- feel free to set that
holder.textView1.setOnClickListener(TextViewListener);
holder.textView2.setOnClickListener(TextViewListener);
holder.textView3.setOnClickListener(TextViewListener);
holder.textView4.setOnClickListener(TextViewListener);
// Set up the rest of the views however you need
/*.....
......
*/
return row;
}
}
getItemAtPosition(pos) returns the view used as each of the item in the listView. According to your xml, the root element is RelativeLayout, so this method will return an RelativeLayout, not a TextView.
what you can do is:
String test = ((TextView)((View)av.getItemAtPosition(pos)).findViewById(R.id.textView1))
.getText().toString();

accessing a specific view from onclick listener inside getView of customer adapter

I want the onclick method to communicate with the other views in my layout. I am getting a NullPointerException when attempting to do so. I think I may be doing it wrong.
My getView method is below:
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
ViewHolder holder;
final int p =position;
if (v == null) {
LayoutInflater vi = (LayoutInflater) _c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.menuitemrow, null);
holder = new ViewHolder();
holder.image = (ImageView) v.findViewById(R.id.MIR_itemImage);holder.image.setTag(position);
holder.itemName = (TextView)v.findViewById(R.id.MIR_itemName);holder.itemName.setTag(position);
holder.itemPrice = (TextView)v.findViewById(R.id.MIR_itemPrice);holder.itemPrice.setTag(position);
holder.itemOther = (TextView)v.findViewById(R.id.MIR_itemOther);holder.itemOther.setTag(position);
holder.details = (Button) v.findViewById(R.id.MIR_detailsbtn);holder.details.setTag(position);
holder.qAdd = (Button) v.findViewById(R.id.MIR_qaddbtn);holder.qAdd.setTag(position);
v.setTag(holder);
} else
holder = (ViewHolder) v.getTag();
MenuItemDetail mid = _data.get(position);
holder.image.setImageResource(mid.icon);
holder.itemName.setText(mid.itemName);
holder.itemPrice.setText("Price: $"+mid.itemPrice);
holder.itemOther.setText(mid.itemOther);
//set click listeners
holder.details.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
m =_data.get(p);
s = m.getItemName();
Toast.makeText(_c, "clicked details"+p+" "+s, Toast.LENGTH_SHORT).show();
}
});
holder.qAdd.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(_c, "clicked quick add "+p, Toast.LENGTH_SHORT).show();
TextView btnUpdate = (TextView) v.findViewById(R.id.CBV_textview2);
//btnUpdate.setTag(1);
//btnUpdate.setText(btnUpdate.getTag().toString());
btnUpdate.setText("b");
}
});
return v;
}
as you can see I am just assigning a number to the getTag just to pass it to the setText method. I have tried it commenting out the set and get, using just a String.
the problematic line is btnUpdate.setText("b");
Any ideas?
to summarize I am trying to access a TextView of a custom Button inside of a custom ListView from its customer adapter's getView method.
UPDATE for comment:
This is a Custom Listview being displayed with other buttons. I want the listview item (a button that is part of the custom ListView) when it is click to update the textview of a custom button I created that is displayed in the same acitivity as the custom list view.
The Layout is below:
<?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" >
<Button
android:id="#+id/MI_backbutton"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:text="#string/back_list_categories"
android:textSize="#dimen/FontSize8" />
<ListView
android:id="#+id/menuList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="20" >
</ListView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_gravity="bottom"
android:layout_marginBottom="5dp"
android:layout_weight="1" >
<Button
android:id="#+id/button2"
style="#android:style/Widget.Button"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_gravity="bottom"
android:layout_weight="1"
android:text="#string/generalbutton" />
<include
android:id="#+id/MI_checkorder"
style="android:buttonStyle"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_gravity="bottom"
android:layout_marginTop="5dp"
android:layout_weight="1"
layout="#layout/custombtnview"
android:background="#style/AppTheme"
android:clickable="true"
android:focusable="true" />
</LinearLayout>
In the view I am referencing to get the Textview, the textview is not there. What is in that view is the MI_checkorder. I was thinking maybe this is why I am getting the NullPointerException for my btnUpdate. the textveiw CBV_textview2 is part of the view I created as a button(MI_checkorder).
I now have the button but I am unable to get the change to show on the UI. Here is the code I added:
public void onClick(View v) {
Toast.makeText(_c, "clicked quick add "+p, Toast.LENGTH_SHORT).show();
TextView btnUpdate = (TextView) inflateToEdit(_c, v).findViewById(R.id.CBV_textview2);
//btnUpdate.setTag(1);
//btnUpdate.setText(btnUpdate.getTag().toString());
btnUpdate.setText("b");
}
and this is the method I created in the custom adapter class.
public View inflateToEdit(Context c, View view){
View vw = view;
LayoutInflater vi = (LayoutInflater) _c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return vi.inflate(R.layout.custombtnview, null);
}
It is updating the button's text but it is not showing on the UI. I do not think i can update the UI from this point in the code. I think I need to do it from the activity.
That's because findViewById called on a view looks for the child view of the given view.
findViewById doc
"Look for a child view with the given id. If this view has the given id, return this view."
From what I can see, CBV_textview2 is contained in the layout of the activity.
So, what you can do is to pass the activity to the adapter's constructor (I see you already have a _c member variable which I suppose is a Context, OR directly pass the CBV_textview2 itself (calling findViewById in the containing activty).
In both cases, you need to call the activity's findViewById which
"Finds a view that was identified by the id attribute from the XML that was processed in onCreate(Bundle)."
public class YourAdapter extends ArrayAdapter<String> {
private final Context context;
// blahblahblah
private final TextView mYourTextView;
public YourAdapter(Context context, String[] values, TextView btnUpdate) {
super(context, R.layout.rowlayout, values);
this.context = context;
this.values = values;
mYourTextView = btnUpdate;
}
....
#Override
public void onClick(View v) {
Toast.makeText(_c, "clicked quick add "+p, Toast.LENGTH_SHORT).show();
//mYourTextView.setTag(1);
//mYourTextView.setText(btnUpdate.getTag().toString());
mYourTextView.setText("b");
}
In your activity you should be able to get the TextView before calling the constructor.

Androids ExpandableListView - where to put the button listener for buttons that are children

I have been playing around a lot with the ExpandableListView and I cannot figure out where to add the button listeners for the button that will be the children in the view. I did manage to get a button listener working that uses getChildView() below, but it seems to be the same listener for all the buttons.
The best case scenario is that I would be able to implement the button listeners in the class that instantiates the ExpandableListAdapter class, and not have to put the listeners in the actual ExpandableListAdapter class. At this point I don't even know if that is possible
I have been experimenting with this tutorial/code: HERE
getChildView()
#Override
public View getChildView(int set_new, int child_position, boolean view, View view1, ViewGroup view_group1)
{
ChildHolder childHolder;
if (view1 == null)
{
view1 = LayoutInflater.from(info_context).inflate(R.layout.list_group_item_lv, null);
childHolder = new ChildHolder();
childHolder.section_btn = (Button)view1.findViewById(R.id.item_title);
view1.setTag(childHolder);
childHolder.section_btn.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
Toast.makeText(info_context, "button pushed", Toast.LENGTH_SHORT).show();
}
});
}else {
childHolder = (ChildHolder) view1.getTag();
}
childHolder.section_btn.setText(children_collection.get(set_new).GroupItemCollection.get(child_position).section);
Typeface tf = Typeface.createFromAsset(info_context.getAssets(), "fonts/AGENCYR.TTF");
childHolder.section_btn.setTypeface(tf);
return view1;
}
Any help would be much appreciated. Thank you and I will be standing by.
If the buttons are in the ExpandableListView, their listener needs to be in the adapter.
I'm not sure the thrust of your question, but if you are asking how do you relate the button to the contents of the child row, I can answer that. :p
I'll assume a somewhat simple child row layout for demonstration purposes.
child_row.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal" >
<TextView
android:id="#+id/ListItem1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="left|center_vertical"
android:paddingLeft="7dip"
android:paddingRight="7dip"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="#+id/ListItem2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="right|center_vertical"
android:textAppearance="?android:attr/textAppearanceLarge" />
<Button
android:id="#+id/button"
android:focusable="false"
android:focusableInTouchMode="false"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
Then, to get the contents of the row when your button is pressed, you use the button to backtrack to the parent vieew and then get the necessary child views and their contents:
childHolder.section_btn.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
LinearLayout ll = (LinearLayout) v.getParent(); // get the view containing the button
TextView tv1 = (TextView) ll.findViewById(R.id.ListItem1); // get the reference to the first widget
TextView tv2 = (TextView) ll.findViewById(R.id.ListItem2); // get the reference to the second widget
String text1 = tv1.getText.toString(); // Get the contents of the first widget to a string
String text2 = tv2.getText.toString(); // Get the contents of the second widget to a string
}
});
If this isn't what you were looking for clarify your question and I'll take another shot at it.

Categories