My App currently displays 4 options (which I want to make clickable) in a single TextView. At the moment, it looks like this (i.e. last message bubble): app picture
Instead of the above, I want to have those options as 4 separate TextViews, like in this example. I researched multiple solutions, but none of them worked for me, because I am using RecyclerView.Adapter. Here is the relevant part:
case OPTION:
String option = "";
option = message.getMessage();
for ( DialogNodeOutputOptionsElement r : message.getOptions() ){
option += r.getLabel()+"<br/>";
((ViewHolder) holder).message.setText(Html.fromHtml(option));
// new TextView for next Option
The whole ChatAdapter looks like this:
public class ChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
protected Activity activity;
private int SELF = 100;
private ArrayList<Message> messageArrayList;
public ChatAdapter(ArrayList<Message> messageArrayList) {
this.messageArrayList = messageArrayList;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView;
// view type is to identify where to render the chat message
// left or right
if (viewType == SELF) {
// self message
itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.chat_item_self, parent, false);
} else {
// WatBot message
itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.chat_item_watson, parent, false);
}
return new ViewHolder(itemView);
}
#Override
public int getItemViewType(int position) {
Message message = messageArrayList.get(position);
if (message.getId() != null && message.getId().equals("1")) {
return SELF;
}
return position;
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
Message message = messageArrayList.get(position);
switch (message.type) {
case TEXT:
((ViewHolder) holder).message.setText(Html.fromHtml(message.getMessage()+"<br/>"));
break;
case IMAGE:
((ViewHolder) holder).message.setVisibility(View.GONE);
ImageView iv = ((ViewHolder) holder).image;
Glide
.with(iv.getContext())
.load(message.getUrl())
.into(iv);
break;
case OPTION:
String option = "";
option = message.getMessage();
for ( DialogNodeOutputOptionsElement r : message.getOptions() ){
option += r.getLabel()+"<br/>";
((ViewHolder) holder).message.setText(Html.fromHtml(option));
// new TextView for next Option
}
break;
case PAUSE:break;
}
}
#Override
public int getItemCount() {
return messageArrayList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView message;
ImageView image;
public ViewHolder(View view) {
super(view);
message = (TextView) itemView.findViewById(R.id.message);
image = (ImageView) itemView.findViewById(R.id.image);
//TODO: Uncomment this if you want to use a custom Font
}
}
}
and the XML looks like this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/linear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<android.support.v7.widget.AppCompatImageView
android:layout_width="54dp"
android:layout_height="match_parent"
android:layout_marginLeft="5dp"
android:src="#mipmap/new_face" />
<TextView
android:id="#+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_marginLeft="5dp"
android:layout_marginTop="5dp"
android:layout_marginRight="5dp"
android:layout_marginBottom="10dp"
android:autoLink="web"
android:background="#drawable/bg_bubble_watbot"
android:fontFamily="sans-serif"
android:textColor="#android:color/black"
android:textIsSelectable="true"
android:textSize="14sp" />
<ImageView
android:id="#+id/image"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
Contex Error
App picture after working code
Try following:
Inside your layout, add a container for your to-be-added TextViews:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/linear"... >
<android.support.v7.widget.AppCompatImageView ... />
<TextView ... />
<LinearLayout android:id="#+id/optionsContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/bg_bubble_watbot"
android:orientation="vertical" />
<ImageView .../>
</LinearLayout>
Then, update your ViewHolder to reference the container:
public class ViewHolder extends RecyclerView.ViewHolder {
...
LinearLayout optionsContainer;
public ViewHolder(View view) {
super(view);
...
optionsContainer = (LinearLayout) itemView.findViewById(R.id.optionsContainer);
}
}
Then, in your OPTION case:
TextView tv = ((ViewHolder) holder).message;
tv.setVisibility(View.GONE));
LinearLayout optionsContainer = ((ViewHolder) holder).optionsContainer;
TextView messageTextView = createTextView(message.getMessage(), optionsContainer.getContext());
for ( DialogNodeOutputOptionsElement r : message.getOptions() ) {
// you should check if r.getLabel() really returns a HTML string
// if not, you will have to enclose it with html tags to make it clickable later
String option = r.getLabel();
TextView optionTextView = createTextView(option, optionsContainer.getContext());
// add the created textView to our container
optionsContainer.addView(optionTextView);
}
Implement the createTextView() function inside your adapter:
private TextView createTextView(String text, Context context) {
TextView tv = new TextView(context);
LinearLayout.LayoutParams params=new LinearLayout.LayoutParams
((int) LinearLayout.LayoutParams.WRAP_CONTENT,(int) LinearLayout.LayoutParams.WRAP_CONTENT);
tv.setLayoutParams(params);
tv.setTextSize((float) 15);
tv.setText(text);
int blueColor = Color.parseColor("#0000ff");
// make text blue
tv.setTextColor(blueColor);
// make text underline
tv.setPaintFlags(tv.getPaintFlags()| Paint.UNDERLINE_TEXT_FLAG);
tv.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Toast.makeText(context, "Link clicked", Toast.LENGTH_SHORT).show();
// add here what the click should do
}
});
return tv;
}
Related
Is it possible to programmatically add a CardViewto a LinearLayout inside a RecyclerView. At present, all the CardViews get added to the RecyclerView, but I want the ones in the screenshot to be added to the LinearLayout instead.
fragment class
public class TabFragmentRV extends android.support.v4.app.Fragment {
RecyclerView mRecyclerView;
public TabFragmentRV() {}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_rv, container, false);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
View v = getView();
assert v != null;
mRecyclerView = v.findViewById(R.id.my_recyclerview);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
super.onActivityCreated(savedInstanceState);
initRVAdapter();
}
private void initRVAdapter(){
List<Object> itemsList = new ArrayList<>();
RVItemsAapter itemsListAdapter = new RVItemsAapter(getContext());
mRecyclerView.setAdapter(itemsListAdapter);
itemsList.add(new RVLineSeparator());
itemsList.add(new SectionHeader("Section E"));
itemsList.add(new SMSmessage("Item E1","Item E1 description"));
itemsList.add(new SMSmessage("Item E2","Item E2 description"));
itemsList.add(new SMSmessage("Item E3","Item E3 description"));
itemsList.add(new SMSmessage("Item E4","Item E4 description"));
itemsList.add(new RVLineSeparator());
itemsListAdapter.setCallSMSFeed(itemsList);
itemsListAdapter.notifyDataSetChanged();
}
}
adapter class
public class RVItemsAapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final static int TYPE_MAINHEADER = 1, TYPE_EXPANDABLE = 2, TYPE_NONEXPANDABLE = 3, TYPE_SECTIONHEADER = 4, TYPE_TABLE = 5, TYPE_SEPARATOR = 6;
private ArrayList callSMSFeed = new ArrayList();
private Context context;
public RVItemsAapter(Context context){this.context=context;}
public void setCallSMSFeed(List<Object> callSMSFeed){
this.callSMSFeed = (ArrayList) callSMSFeed;
}
#Override
public int getItemViewType(int position) {
if (callSMSFeed.get(position) instanceof MainHeader) {
return TYPE_MAINHEADER;
} else if (callSMSFeed.get(position) instanceof Phonecall) {
return TYPE_EXPANDABLE;
} else if (callSMSFeed.get(position) instanceof SMSmessage) {
return TYPE_NONEXPANDABLE;
} else if (callSMSFeed.get(position) instanceof SectionHeader) {
return TYPE_SECTIONHEADER;
} else if (callSMSFeed.get(position) instanceof MyTable) {
return TYPE_TABLE;
} else if (callSMSFeed.get(position) instanceof RVLineSeparator) {
return TYPE_SEPARATOR;
}
throw new IllegalArgumentException("Item at position " + position + " is not an instance of either Phonecall or SMSmessage");
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
int viewType=holder.getItemViewType();
switch (viewType){
case TYPE_MAINHEADER:
MainHeader mainHeader = (MainHeader) callSMSFeed.get(position);
((MHeaderViewHolder)holder).showMHeaderDetails(mainHeader);
break;
case TYPE_EXPANDABLE:
Phonecall call = (Phonecall) callSMSFeed.get(position);
((CallViewHolder)holder).showCallDetails(call);
break;
case TYPE_NONEXPANDABLE:
SMSmessage sms = (SMSmessage) callSMSFeed.get(position);
((SMSViewHolder)holder).showSmsDetails(sms);
break;
case TYPE_SECTIONHEADER:
SectionHeader sectionHeader = (SectionHeader) callSMSFeed.get(position);
((SectionViewHolder)holder).showSectionDetails(sectionHeader);
break;
case TYPE_TABLE:
TableToilets tblToilets = (TableToilets) callSMSFeed.get(position);
((TblViewHolder)holder).showTblDetails(tblToilets);
break;
case TYPE_SEPARATOR:
((SeparatorViewHolder)holder).showSeparatorDetails();
break;
default:
throw new IllegalArgumentException("unexpected viewType: " + viewType);
}
}
#Override
public int getItemCount(){return callSMSFeed.size();}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
int layout;
RecyclerView.ViewHolder viewHolder;
switch (viewType){
case TYPE_MAINHEADER:
layout = R.layout.rv_header;
View mainheaderView = LayoutInflater
.from(parent.getContext())
.inflate(layout, parent, false);
viewHolder = new MHeaderViewHolder(mainheaderView);
break;
case TYPE_EXPANDABLE:
layout = R.layout.cardview_dualline_withexpandability;
View callsView = LayoutInflater
.from(parent.getContext())
.inflate(layout, parent, false);
viewHolder = new CallViewHolder(callsView);
break;
case TYPE_NONEXPANDABLE:
layout = R.layout.cardview_dualline_sansexpandability;
View smsView = LayoutInflater
.from(parent.getContext())
.inflate(layout, parent, false);
viewHolder = new SMSViewHolder(smsView);
break;
case TYPE_SECTIONHEADER:
layout = R.layout.sectionheaderforrecyclerview;
View sectionheaderView = LayoutInflater
.from(parent.getContext())
.inflate(layout, parent, false);
viewHolder = new SectionViewHolder(sectionheaderView);
break;
case TYPE_TABLE:
layout = R.layout.cardview_tableview_withexpandability;
View tblView = LayoutInflater
.from(parent.getContext())
.inflate(layout, parent, false);
viewHolder = new TblViewHolder(tblView);
break;
case TYPE_SEPARATOR:
layout = R.layout.lineseparatorforrecyclerview;
View separatorView = LayoutInflater
.from(parent.getContext())
.inflate(layout, parent, false);
viewHolder = new SeparatorViewHolder(separatorView);
break;
default:
throw new IllegalArgumentException("unexpected viewType: " + viewType);
}
return viewHolder;
}
// First ViewHolder of object type Call
// Reference to the views for each call items to display desired information
public class CallViewHolder extends RecyclerView.ViewHolder {
final Typeface iconFont = FontManager.getTypeface(context, FontManager.FONTAWESOME);
private TextView arrowexpandcollapseTextView, callerNameTextView, callTimeTextView;
private LinearLayout llFacilityInformation;
CallViewHolder(View itemView) {
super(itemView);
// Initiate view
arrowexpandcollapseTextView = itemView.findViewById(R.id.tv_cvwithexpandability_arrowexpandcollapse);
callerNameTextView = itemView.findViewById(R.id.tv_cvwithexpandability_title);
callTimeTextView = itemView.findViewById(R.id.tv_cvwithexpandability_subtitle);
llFacilityInformation = itemView.findViewById(R.id.ll_cvwithexpandability_subtitle);
arrowexpandcollapseTextView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (llFacilityInformation.getVisibility() == View.GONE) {
expandLL(llFacilityInformation, arrowexpandcollapseTextView);
} else {
collapseLL(llFacilityInformation, arrowexpandcollapseTextView);
}
}
});
callerNameTextView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (llFacilityInformation.getVisibility() == View.GONE) {
expandLL(llFacilityInformation, arrowexpandcollapseTextView);
} else {
collapseLL(llFacilityInformation, arrowexpandcollapseTextView);
}
}
});
llFacilityInformation.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (llFacilityInformation.getVisibility() == View.GONE) {
expandLL(llFacilityInformation, arrowexpandcollapseTextView);
} else {
collapseLL(llFacilityInformation, arrowexpandcollapseTextView);
}
}
});
}
void showCallDetails(Phonecall call){
// Attach values for each item
arrowexpandcollapseTextView.setText(R.string.fa_icon_chevron_down);
arrowexpandcollapseTextView.setTypeface(iconFont);
llFacilityInformation.setVisibility(View.GONE);
String callerName = call.getCallerName();
String callTime = call.getCallTime();
callerNameTextView.setText(callerName);
callTimeTextView.setText(callTime);
}
}
// Third ViewHolder of object type SectionHeader
// Reference to the views for each call items to display desired information
public class SectionViewHolder extends RecyclerView.ViewHolder {
final Typeface iconFont = FontManager.getTypeface(context, FontManager.FONTAWESOME);
private LinearLayout llSectionWithCards;
private TextView arrowexpandcollapseTextView, sectionNameTextView;
SectionViewHolder(View itemView) {
super(itemView);
// Initiate view
arrowexpandcollapseTextView = itemView.findViewById(R.id.tv_sectionheaderforrv_expandcollapsearrow);
sectionNameTextView = itemView.findViewById(R.id.tv_sectionheaderforrv_title);
arrowexpandcollapseTextView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (llSectionWithCards.getVisibility() == View.GONE) {
expandLL(llSectionWithCards, arrowexpandcollapseTextView);
} else {
collapseLL(llSectionWithCards, arrowexpandcollapseTextView);
}
}
});
}
void showSectionDetails(SectionHeader section){
// Attach values for each item
arrowexpandcollapseTextView.setText(R.string.fa_icon_chevron_down);
arrowexpandcollapseTextView.setTypeface(iconFont);
String sectionName = section.getSectionName();
sectionNameTextView.setText(sectionName);
}
}
// Fifth ViewHolder of object type RVLineSeparator
// Reference to the views for each call items to display desired information
public class SeparatorViewHolder extends RecyclerView.ViewHolder {
private View lSeparator;
SeparatorViewHolder(View itemView) {
super(itemView);
lSeparator = itemView.findViewById(R.id.rv_lineseparator);
}
void showSeparatorDetails(){
TypedValue tValueD = new TypedValue();
context.getTheme().resolveAttribute(R.attr.dividerColor, tValueD, true);
lSeparator.setBackgroundResource(tValueD.resourceId);
}
}
private void expandGroup(final TextView arrowexpandcollapseTextView) {
?
}
private void collapseGroup(final TextView arrowexpandcollapseTextView) {
?
}
private void expandLL(final LinearLayout llFacilityInformation, final TextView arrowexpandcollapseTextView) {
llFacilityInformation.setVisibility(View.VISIBLE);
arrowexpandcollapseTextView.setText(R.string.fa_icon_chevron_up);
}
private void collapseLL(final LinearLayout llFacilityInformation, final TextView arrowexpandcollapseTextView) {
llFacilityInformation.setVisibility(View.GONE);
arrowexpandcollapseTextView.setText(R.string.fa_icon_chevron_down);
}
}
section header layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/ll_sectionwithexpandability_main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/ll_sectionheader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp"
android:weightSum="100">
<TextView
android:id="#+id/tv_sectionheader_expandcollapsearrow"
android:clickable="true"
android:focusable="true"
android:layout_weight="10"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textColor="?android:attr/textColorPrimary"
style="#android:style/TextAppearance.Large" />
<TextView
android:id="#+id/tv_sectionheader_title"
android:layout_weight="90"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="#android:style/TextAppearance.Large" />
</LinearLayout>
<!-- I WANT ALL THE CARD VIEWS TO BE ADDED INSIDE THIS LINEARLAYOUT (ll_section_cards) -->
<LinearLayout
android:id="#+id/ll_section_cards"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
RecyclerView layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/linearLayout_recyclerView"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/my_recyclerview"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
SMSmessage class
public class SMSmessage {
private String senderName, smsContent;
public SMSmessage(String senderName, String smsContent) {
this.senderName = senderName;
this.smsContent = smsContent;
}
public String getSenderName() {
return senderName;
}
public String getSmsContent() {
return smsContent;
}
}
First use one parent layout that is linear layout then in that create child layout which contain any layout like linear or relative then of second module you can create second child layout and in child layout you will use card view and other component like textview or edit text inside the card view
Otherwise constrain layout is best to develop this layout
For example
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/ll_sectionwithexpandability_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="2"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="wrap_content">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/ll_sectionheader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp"
android:weightSum="100">
<TextView
android:id="#+id/tv_sectionheader_expandcollapsearrow"
android:clickable="true"
android:focusable="true"
android:layout_weight="10"
android:text="Testing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textColor="?android:attr/textColorPrimary"
style="#android:style/TextAppearance.Large" />
<TextView
android:id="#+id/tv_sectionheader_title"
android:layout_weight="90"
android:layout_width="0dp"
android:text="testing"
android:layout_height="wrap_content"
style="#android:style/TextAppearance.Large" />
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
<!-- I WANT ALL THE CARD VIEWS TO BE ADDED INSIDE THIS LINEARLAYOUT (ll_section_cards) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="wrap_content">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="#+id/ll_section_cards"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="#+id/te"
android:layout_weight="90"
android:layout_width="wrap_content"
android:text="testing"
android:layout_height="wrap_content"
style="#android:style/TextAppearance.Large" />
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
</LinearLayout>
EDIT: Your question falls under the XY-Problem : enter link description here
Your entire architecture/pattern doesn't seem to fit right.
You may be able to adjust your code so in the "OnBind" method, you check to see if the next n+1 object is an SMSMessage and add the lot to the linear layout...and somehow have a record that they have already been "bound" to avoid double binding...but then you'd also need to detect when that viewholder is unbound...which all seems like fighting the way the framework wants you to do things.
I'd suggest having the API (or a "local service") convert the API data (preferably using ObjectMapper like JacksonMapper) to a class that looks more like:
import java.util.List;
public class SMSMessageCollection {
private List<SMSMessage> smsMessages;
private String headerText;
private boolean hasStartingRVLine;
private boolean hasEndingRVLine;
public SMSMessageCollection(String headerText, boolean hasStartingRVLine, boolean hasEndingRVLine) {
this.headerText = headerText;
this.hasStartingRVLine = hasStartingRVLine;
this.hasEndingRVLine = hasEndingRVLine;
}
//CUSTOM "ADDER":
public void addSMSMessagesToCollection(List<SMSMessage> smsMessagesToAdd){
this.smsMessages.addAll(smsMessagesToAdd);
}
//GETTER AND SETTER HERE -> OR USE LOMBOK...
}
This class contains a List<SMSMessages> so when the object is being bound in the recycler adapter, you can iterate through the list and add them to the linear layout.
So here is what my Activity would look like...taking the API data and making a list of SMSMessageCollection(s) and then this could be passed to the Adapter with one ViewHolder...
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.json.JSONArray;
import java.io.IOException;
import java.util.List;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//This would be fetched using say Volley...and the structure should MATCH the class we have made...
//Otherwise...make a "service class" to convert the API response to the SMSMessageCollection
JSONArray apiDataModels = new JSONArray();
// Jackson ObjectMapper will turn the API data in to a List or POJOs...in like two lines.
try {
List<SMSMessageCollection> smsMessageCollections = new ObjectMapper()
.readValue(apiDataModels.toString(),new TypeReference<List<SMSMessageCollection>>(){});
} catch (IOException e) {
e.printStackTrace();
}
}
}
So in the OnBindViewHolder method in the adpater, you can get the List for that section and add them programitcally to the LienarLayout of the ViewHolder being injected.
I have an xml for generating a listview with a checkbox as below:
<?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:orientation="vertical"
android:padding="10dp" >
<CheckBox
android:id="#+id/book_check"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:checked="false" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="#+id/book_check"
android:orientation="horizontal"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" >
<ImageView
android:id="#+id/imageView1"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:src="#drawable/finger" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/book_title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="5dp"
android:text="Title"
android:textColor="#000"
android:textSize="22sp" />
<TextView
android:text="Description"
android:id="#+id/book_descri"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="#FF2500"
android:textSize="18sp" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
In my custom class I have the following code to help me manage the data I am getting from my little database
private class SbListAdapter extends ArrayAdapter<SbItem> {
ArrayList<SbItem> SongBookList;
public SbListAdapter(Context context, int textViewResourceId,
ArrayList<SbItem> SongBookList) {
super(context, textViewResourceId, SongBookList);
this.SongBookList = new ArrayList<SbItem>();
this.SongBookList.addAll(SongBookList);
}
private class ViewHolder {
CheckBox book;
TextView title;
TextView descri;
}
#SuppressLint("InflateParams")
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null; Log.v("ConvertView", String.valueOf(position));
if (convertView == null) {
LayoutInflater vi = (LayoutInflater)getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.cc_list_select, null);
holder = new ViewHolder();
holder.title = (TextView) convertView.findViewById(R.id.book_title);
holder.descri = (TextView) convertView.findViewById(R.id.book_descri);
holder.book = (CheckBox) convertView.findViewById(R.id.book_check);
convertView.setTag(holder);
holder.book.setOnClickListener( new View.OnClickListener() {
public void onClick(View v) {
CheckBox cb = (CheckBox) v ;
SbItem SbItem = (SbItem) cb.getTag();
SbItem.setSelected(cb.isChecked());
}
});
} else {
holder = (ViewHolder) convertView.getTag();
}
SbItem SbItem = SongBookList.get(position);
holder.title.setText(SbItem.getBook());
holder.descri.setText(SbItem.getDescri());
holder.book.setChecked(SbItem.isSelected());
holder.book.setTag(SbItem);
return convertView;
}
}
My desire is that when the text in line one and that in line is clicked that the checkbox is checked or unchecked because currently this only works when only the checkbox is clicked.
If I try to type cast it just gives an error. For instance this can not work:
holder.title.setOnClickListener( new View.OnClickListener() {
public void onClick(View v) {
CheckBox cb = (CheckBox) v ;
SbItem SbItem = (SbItem) cb.getTag();
SbItem.setSelected(cb.isChecked());
}
});
I just needs to work when users click anywhere on that item just like in settings activity.
I have tried to do this:
convertView.setOnClickListener( new View.OnClickListener() {
public void onClick(View v) {
CheckBox cb = (CheckBox) v ;
SbItem SbItem = (SbItem) cb.getTag();
SbItem.setSelected(cb.isChecked());
}
});
but when debugging the crashes and the error on the console is:
08-31 13:25:19.793: E/AndroidRuntime(24214):
java.lang.ClassCastException: android.widget.RelativeLayout cannot be
cast to android.widget.CheckBox
Try this:
From your activity or wherever you init your listview:
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
//here update the status of your SbItem in the position of your arraylist and then notify the adapter
sblist.get(position).setSelected(!sblist.get(position).isSelected());
adapter.notifyDataSetChanged();
}
});
Also remove all your click listeners from your adapter.
UPDATE
In your SbItem class add setter:
public void setSelected(boolean s){
this.selected=s;
}
Also i assume that your getter returns the selected property
public boolean isSelected(){
return selected;
}
Use below event for change CheckBox State
holder.checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
checkbox.setChecked(true);
} else {
checkbox.setChecked(false);
}
}
});
As your question you need to change your code.
convertView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
holder.book.performClick();
}
});
Now use setOnCheckedChangeListener for checkbox.
holder.book.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
buttonView.setChecked(true);
// do your stuff
} else {
buttonView.setChecked(false);
// do your stuff
}
}
});
I'm removing images from a gridview when a button is pressed.
The images does remove fine and the gridview also updates with the call "adapter.notifyDataSetChanged();".
When an imageview is removed another image next to this position should take its position. This happens, but the image here won't reload so there's just a blank space? How can I get this imageview to reload its image?
The problem:
Here is my gridadapter:
public class FavoriteMovieGridAdapter extends BaseAdapter {
Context context;
ArrayList<DataFavorites> List;
FavoritesMovie fragment;
private static LayoutInflater inflater = null;
FavoriteMovieGridAdapter adapter = this;
public FavoriteMovieGridAdapter(Context context, ArrayList<DataFavorites> List, FavoritesMovie fragment) {
this.context = context;
this.List = List;
this.fragment = fragment;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
// TODO Auto-generated method stub
return List.size();
}
#Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return position;
}
#Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
// Avoid unneccessary calls to findViewById() on each row
final ViewHolder holder;
/*
* If convertView is not null, reuse it directly, no inflation
* Only inflate a new View when the convertView is null.
*/
if (convertView == null) {
convertView = inflater.inflate(R.layout.favorite_grid_item, null);
holder = new ViewHolder();
holder.poster = (ImageView) convertView.findViewById(R.id.upcoming_image);
holder.editbutton = (ImageView) convertView.findViewById(R.id.delete_item);
// The tag can be any Object, this just happens to be the ViewHolder
convertView.setTag(holder);
}
else{
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
holder = (ViewHolder) convertView.getTag();
}
final View finalConvertView = convertView;
final DataFavorites e;
new DataFavorites();
e = List.get(position);
String url = String.valueOf(e.getUrl());
// load image url into poster
// Seems as if this doesn't run for the imageview next to this when this view is removed?
Picasso.with(context).load(url).fit().placeholder(R.drawable.movie_back).into(holder.poster);
// Create onclick and show edit button
convertView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
// show edit button
holder.editbutton.setVisibility(View.VISIBLE);
YoYo.with(Techniques.FadeIn).duration(700).playOn(finalConvertView.findViewById(R.id.delete_item));
// onclick edit button remove item and update gridview
holder.editbutton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
YoYo.with(Techniques.ZoomOut).duration(700).playOn(finalConvertView.findViewById(R.id.favorite_relative));
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
adapter.List.remove(e);
adapter.notifyDataSetChanged();
// Delete specific movie from data base
DatabaseHandlerMovie db = new DatabaseHandlerMovie(context);
// Reading all movies
db.deleteMovie(e);
}
}, 1000);
}
});
return false;
}
});
return convertView;
}
static class ViewHolder {
ImageView poster;
ImageView editbutton;
}
}
My grid item layout:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="#+id/favorite_relative"
android:layout_centerHorizontal="true"
android:gravity="center_horizontal">
<ImageView android:layout_width="123.3dp"
android:layout_height="185.3dp"
android:id="#+id/upcoming_image"
android:scaleType="fitXY" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/delete_item"
android:src="#drawable/ic_remove_circle_outline_white_24dp"
android:tint="#color/colorPrimaryDark"
android:clickable="true"
android:visibility="gone"
android:layout_alignRight="#+id/upcoming_image"
android:layout_alignEnd="#+id/upcoming_image"/>
</RelativeLayout>
My grid layout:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context=".Favorites"
android:background="#FFFFFF"
android:focusableInTouchMode="true">
<GridView
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:id="#+id/movies_gridlayout"
android:paddingRight="-1dp"
android:paddingEnd="-1dp"
android:numColumns="3"
android:background="#ffffff"
android:visibility="invisible"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:id="#+id/favorite_none"
android:text="No favorite movies found"
android:textSize="15sp"
android:visibility="gone"
android:textColor="#color/SecondaryText"
/>
<ProgressBar
android:id="#+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
>
</ProgressBar>
</FrameLayout>
Turns out a 3rd-party lib was causing this:
YoYo.with(Techniques.ZoomOut).duration(700).playOn(finalConvertView.findViewById(R.id.favorite_relative));
you will have adapter and mainactivity for gridview
So u will have to implement public interface in both of them to communicate
following code may help you :)
In Adapter class add following code
private OnItemClickListner onItemClickListner;
public MyAdapter(Context c, List<PDFDoc> pdfDocs,OnItemClickListner onItemClickListner) {
this.context = c;
this.pdfDocs = pdfDocs;
this.onItemClickListner=onItemClickListner;
}
public interface OnItemClickListner {
void removefromadapter(int position);
}
ImageButton imageButton = view.findViewById(R.id.cancelid);
imageButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// write your stuff here
onItemClickListner.removefromadapter(position);
}
});
In MainActivity of gridview add following code
implements OnItemClickListner required**
public class addclient_fragment extends Fragment implements MyAdapter.OnItemClickListner {
you will required to add this method in mainactivity
#Override
public void removefromadapter(int position) {
// write your stuff here
}
I have a gridView witch contains a group of images with two columns, what I want to do is to put a title for a group of images.
Now I can do that but the problem that title is that the title is only in one column and the second column is always empty, how can I put the title in the two columns?
Here is my adapter:
public class MultimediaPhotosAdapter extends BaseAdapter {
private Context mContext;
private List<ImagesDto> imagesDto = new ArrayList<ImagesDto>();
ImagesFlickrDto imagesFlickrDto;
final String formatImage = ImagesNameFormat.FORMAT_IMAGE_DEFAULT.getImagesNameFormat();
ImagesFormatsDto currentImageFormatToDisplay;
GridView gridViewImages;
public MultimediaPhotosAdapter(Context context, GridView gridViewImages) {
this.gridViewImages = gridViewImages;
this.mContext = context;
}
#Override
public int getCount() {
return imagesDto.size();
}
#Override
public Object getItem(int position) {
return imagesDto.get(position);
}
#Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return 0;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View itemView = null;
HolderElementGridView holderElementGridView;
ImagesDto currentImage = imagesDto.get(position);
if(convertView == null) {
holderElementGridView = new HolderElementGridView();
itemView = View.inflate(mContext,R.layout.item_multimedia_photo,null);
holderElementGridView.image = (NetworkImageView) itemView.findViewById(R.id.imageView);
holderElementGridView.title = (TextView) itemView.findViewById(R.id.title);
itemView.setTag(holderElementGridView);
}
else {
itemView = convertView;
holderElementGridView = (HolderElementGridView)itemView.getTag();
}
if(imagesDto.get(position).isFirstElementOfCategorie()){
holderElementGridView.image.setVisibility(View.GONE);
holderElementGridView.title.setVisibility(View.VISIBLE);
holderElementGridView.title.setText(currentImage.getTitle());
}
else if(imagesDto.get(position).getUrl()!=null){
holderElementGridView.image.setVisibility(View.VISIBLE);
holderElementGridView.title.setVisibility(View.GONE);
holderElementGridView.image.setImageUrl(StringFormat.getUrlImage(mContext, currentImage.getUrl(), currentImageFormatToDisplay.getSuffix(), currentImageFormatToDisplay.getExtension()),VolleySingleton.getInstance(mContext).getImageLoader());
holderElementGridView.image.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
DrawerLayoutInterface screenInterface=(DrawerLayoutInterface)mContext;
if(screenInterface!=null)
screenInterface.showDiaporama(imagesFlickrDto,position);
}
});
}
// column near title is empty
else{
holderElementGridView.image.setVisibility(View.VISIBLE);
holderElementGridView.title.setVisibility(View.GONE);
}
return itemView;
}
public void update(ImagesFlickrDto imagesFlickrDto){
this.imagesFlickrDto = imagesFlickrDto;
this.imagesDto = imagesFlickrDto.getListImages();
AdministrerImagesSA administrerImages = new AdministrerImagesSAImpl();
currentImageFormatToDisplay = administrerImages.getFormatByProriete(imagesFlickrDto.getImagesFormatsDto(), formatImage);
}
class HolderElementGridView{
NetworkImageView image;
TextView title;
}
}
item_multimedia_photo.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:geekui="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<com.netcosports.anderlecht.activity.utils.TypefaceTextView
android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="#dimen/text_size_item_menu"
android:visibility="gone"
android:layout_marginLeft="5dp"
geekui:customTypeface="fonts/DIN-Regular.otf" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.android.volley.toolbox.NetworkImageView
android:id="#+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginRight="5dp"
android:layout_marginLeft="5dp"
android:src="#drawable/ic_launcher" >
</com.android.volley.toolbox.NetworkImageView>
</LinearLayout>
</LinearLayout>
Why everytime the onItemClick() event is invoked on a ListView with a custom adapter, the getView() method in the adapter is called again?
I have the following code in API level 7, and when I try to change the checked value in the row's CheckedTextView object (stored in a ViewHolder), the adapter's getView() for each row is invoked and I got a strange behaviour(The selected row in the listView check/uncheck the CheckedTextView in another row) :
The code for the Activity is:
public class EnvioImagenesActivity extends Activity implements OnItemClickListener {
ListView listView;
private EnvioImagenAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.envio_imagenes);
listView=(ListView) findViewById(R.id.listViewEnvios);
adapter=new EnvioImagenAdapter(this,Store.getImages());
listView.setAdapter(adapter);
listView.setOnItemClickListener(this);
}
public void onItemClick(AdapterView<?> arg0, View view, int pos,
long id) {
ViewHolder holder=(ViewHolder) view.getTag();
holder.checkedTextView.toggle();
}
}
The code for the Xml layout activity is:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:orientation="vertical">
<TextView
android:id="#+id/textView1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/envioImagenes"
android:textAppearance="?android:attr/textAppearanceLarge" android:gravity="center"/>
<ListView
android:id="#+id/listViewEnvios"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:choiceMode="multipleChoice">
</ListView>
</LinearLayout>
The listview adapter:
public class EnvioImagenAdapter extends BaseAdapter {
private List<ImageUri> items;
private Context context;
public EnvioImagenAdapter(Context context, List<ImageUri> items) {
this.context=context;
this.items = items;
}
public int getCount() {
return items.size();
}
public Object getItem(int position) {
return items.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
System.out.println("---getView() method called");
View v = convertView;
ViewHolder holder;
if (v == null) {
LayoutInflater vi = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.row_envio_imagen, null);
holder = new ViewHolder();
holder.imageView = (ImageView) v.findViewById(R.id.imageView1);
holder.textView = (TextView) v.findViewById(R.id.textView1);
holder.temporalProgressBar = (ProgressBar) v
.findViewById(R.id.progressBar1);
holder.checkedTextView = (CheckedTextView) v
.findViewById(R.id.checkedTextView12);
holder.selected = true;
holder.position = position;
v.setTag(holder);
} else {
holder = (ViewHolder) v.getTag();
}
ImageUri imageUri = items.get(position);
File f = new File(imageUri.getImageUri().getPath());
String fileSize = Util.formatFileSize(f.length());
holder.textView.setText(fileSize);
new ImageTask(imageUri).execute(holder);
return v;
}
class ImageTask extends AsyncTask<ViewHolder, Void, Bitmap> {
public ImageTask(ImageUri imageUri) {
this.imageUri = imageUri;
}
private ImageUri imageUri;
private ViewHolder viewHolder;
#Override
protected Bitmap doInBackground(ViewHolder... params) {
viewHolder = params[0];
Bitmap bmp = BitmapFactory.decodeFile(imageUri.getThumbUri()
.getPath());
return bmp;
}
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
viewHolder.temporalProgressBar.setVisibility(View.GONE);
viewHolder.imageView.setVisibility(View.VISIBLE);
viewHolder.imageView.setImageBitmap(result);
};
}
class ViewHolder {
public int position;
CheckedTextView checkedTextView;
ProgressBar temporalProgressBar;
ImageView imageView;
TextView textView;
boolean selected;
}
}
And the xml layout for each row is:
<?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="?android:attr/listPreferredItemHeight"
android:padding="6dip" >
<ProgressBar
android:id="#+id/progressBar1"
android:layout_width="45dip"
android:layout_height="45dip"
android:layout_alignParentLeft="true" />
<ImageView
android:id="#+id/imageView1"
android:layout_width="45dip"
android:layout_height="45dip"
android:layout_alignParentLeft="true" />
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="5dip"
android:layout_toRightOf="#id/imageView1" />
<CheckedTextView
android:id="#+id/checkedTextView12"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkMark="?android:attr/textCheckMark"
android:checked="true"
android:layout_alignParentRight="true">
</CheckedTextView>
<ProgressBar
android:id="#+id/progressBar2"
style="?android:attr/progressBarStyleHorizontal"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_below="#+id/textView1"
android:layout_toLeftOf="#id/checkedTextView12"
android:layout_toRightOf="#id/imageView1"
android:paddingLeft="5dip"
android:paddingTop="2dip" />
</RelativeLayout>
And when I run the app in the simulator and make click in a row, I obtain in the LogCat:
System.out(2947): ---getView() method called
for each row displayed in the list.
Thanks in advance!!!!
I had a similar problem in the code caused
ListView.setChoiceMode (ListView.CHOICE_MODE_SINGLE);
When you click on the item for each item getview summoned again, in your case, you can try to remove android:choiceMode="multipleChoice"
Maybe this will help you.
I have noticed that accessing the views from outside the getView() rarely produces the expected behaviour. I suppose it is because of the recycling mechanism. Two alternative options work:
1) implement onClick() in getView()
2) If you want to keep your logic out of the adapter, in onItemClick(), change the underlying data and call adapter.notifyDatasetChanged(). The checkbox will toggle.