FrameLayout for some reason doesn't work perfectly in listview. I got this incorrect displaying in emulator. In Android Studio items looks nicely. Please help me :)
In Android Studio
In emulator
item.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="100dp"
android:padding="10dp">
<TextView
android:id="#+id/txtName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Title"
android:layout_gravity="center"
android:textSize="27sp"
android:textStyle="bold"/>
<TextView
android:id="#+id/txtGenre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Genre"
android:layout_gravity="bottom|right"
android:textSize="18sp"
android:textStyle="italic"/>
<TextView
android:id="#+id/txtId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ID"
android:textSize="18sp"
android:textStyle="italic"
android:layout_gravity="bottom|left"/>
</FrameLayout>
Adapter code
public class MyFilmListAdapter extends ArrayAdapter<Film> {
Context context;
ArrayList<Film> films;
public MyFilmListAdapter(#NonNull Context context, ArrayList<Film> films) {
super(context, R.layout.item_film, films);
this.context = context;
this.films = films;
}
#NonNull
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
Film f = films.get(position);
LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(R.layout.item_film, null, false);
TextView name = v.findViewById(R.id.txtName);
TextView genre = v.findViewById(R.id.txtGenre);
TextView id = v.findViewById(R.id.txtId);
name.setText(f.getName() + "");
genre.setText(f.getGenre() + "");
id.setText(f.getId() + "");
return v;
}
}
I tried to use LinerLayout and it has correct displaying, however I need FrameLayout.
According to the official documentation
FrameLayout is designed to block out an area on the screen to display a single item. Generally, FrameLayout should be used to hold a single child view, because it can be difficult to organise child views in a way that's scalable to different screen sizes without the children overlapping each other.
You can, however, add multiple children to a FrameLayout and control their position within the FrameLayout by assigning gravity to each child, using the android:layout_gravity attribute.
So you can use LinearLayout inside it.
and nowadays better is you should use Recyclerview with ConstraintLayout
Related
I'm trying to make Recyclerview like Shareit app. It uses sticky header and something like Cardview. I searched and found somethings for implementing sticky header with itemdecoration and it's okay. But for Cardview part, I tried to make nested Recyclerview but performance was so bad, Because using two vertical Recyclerview is not good at all. Is there any better way to achieve this?
The problem with performance is for when i scroll down. It makes lag on creating second item in parent Recyclerview.
AppheaderAdapter:
#Override
public AppHeaderAdapter.HeaderHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_media_header, parent, false);
HeaderHolder headerHolder = new HeaderHolder(view);
return headerHolder;
}
class HeaderHolder extends RecyclerView.ViewHolder { //story
final ImageView arrow;
final TextView tarikh;
final TextView tedad;
final CheckBox tick;
final RecyclerView item_recyc;
HeaderHolder(View itemView) {
super(itemView);
arrow = itemView.findViewById(R.id.header_arrow);
tarikh = itemView.findViewById(R.id.header_tarikh);
tedad = itemView.findViewById(R.id.header_tedad);
tick = itemView.findViewById(R.id.header_tick);
item_recyc = itemView.findViewById(R.id.header_recyc);
}
}
#Override
public void onBindViewHolder(#NonNull AppHeaderAdapter.HeaderHolder headerHolder, int position) {
final AppHeader appHeader = appHeaders.get(position);
...
GridLayoutManager gridLayoutManager = new GridLayoutManager(context, span);
gridLayoutManager.setRecycleChildrenOnDetach(true);
headerHolder.item_recyc.setLayoutManager(gridLayoutManager);
headerHolder.item_recyc.setNestedScrollingEnabled(false);
AppItemAdapter adapter = new AppItemAdapter(context, appHeader.getAppList(), fr_parent, width);
headerHolder.item_recyc.swapAdapter(adapter, true);
...
}
item_media_header.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/header_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="#drawable/so_round_so_white"
app:cardCornerRadius="10dp"
app:cardElevation="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/header_arrow"
android:layout_width="30dp"
android:layout_height="30dp"
android:padding="8dp"
android:src="#drawable/header_arrow"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/header_tarikh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="#dimen/fragment_file"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="#id/header_arrow"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/header_tedad"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="2dp"
android:textColor="#AAAAAA"
android:textSize="#dimen/fragment_file"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="#id/header_tarikh"
app:layout_constraintTop_toTopOf="parent" />
<CheckBox
android:id="#+id/header_tick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:button="#null"
android:clickable="false"
android:drawableRight="#drawable/file_checkbox"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/header_recyc"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.cardview.widget.CardView>
EDIT 1:
I find that just first Cardview is loaded and second one is not loaded until I scroll to end of first one and then load second CardView item and it lags because of second RecyclerView.
I found that Shareit use previous created adapter and just change its internal datalist, So i do that, but still has lag on second item.
Edite 2:
I use one time created adapter on all RecyclerView but still has lag.
Edit 3:
It's a known bug that innerRecyclerView load all items at first and that cause my lag too. because inner Recyclerview is inside NestedScrollView (RV) and it loads all content.Using constant height doesn't work, But I'll update question if I find something better.
You can achieve this with ConstraintLayout and Flow.
EDIT: Sorry, I just realised I wrote everything in Kotlin. If you cannot understand something I can try to translate it to Java.
xml:
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintLayout"
...>
<androidx.constraintlayout.helper.widget.Flow
android:id="#+id/flow"
app:flow_wrapMode="aligned"
app:flow_horizontalStyle="spread_inside"
app:flow_maxElementsWrap="4"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Adding of items:
val constraintLayout = itemView.findViewById(R.id.constraintLayout)
val flow = itemView.findViewById(R.id.flow)
val items // items given to the viewHolder to display
val layoutInflater = LayoutInflater.from(context)
items.forEach { item ->
val view = // create item view, e.g. DataBinding, ...
view.id = ViewCompat.generateViewId()
constraintLayout.addView(root)
flow.addView(root)
}
}
// if there are less than 4 items you have to add invisible dummy items. Otherwise, the alignment will look different for these ViewHolders
if(items.size>=4) return
for (i in 0..(4-items.size)) {
val view = // create dummy view
view.id = ViewCompat.generateViewId()
constraintLayout.addView(root)
flow.addView(root)
}
So far, Using vertical RV inside vertical RV has bug. Because of inner RV make long height and outer RV has no idea of this, So it create just first Item, but all items in inner RV. using all solution on Stack O.F. had no effect!
My issue:
In my xml file, I define android:visibility="gone" in the linear layout labelled as assess_layout_list. Then, in the onClick() of course_adapter_layout, the whole view, I set the visibility back to View.VISIBLE, which does not work, even though the Log call just before it works, the LinearLayout object called assess_list_layout is not null, and it does work when I define the visibility="invisible" in the xml file. I want it to be gone at first though, and visible after clicking as this fits the design of the app.
Here is my course_adapter_view.xml file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/course_adapter_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="left"
>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginTop="20dp"
android:padding="15dp"
android:elevation="2dp"
android:background="#drawable/course_header_background">
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:src="#drawable/course_color_circle"/>
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.25"/>
<TextView
android:id="#+id/course_adapter_course_code"
android:text="TextView1"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_weight="0.5"/>
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.25"/>
<TextView
android:id="#+id/course_adapter_course_title"
android:text="TextView2"
android:layout_width="wrap_content"
android:layout_height="20dp"/>
</LinearLayout>
<LinearLayout
android:id="#+id/assess_list_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginRight="40dp"
android:layout_marginLeft="40dp"
android:background="#drawable/course_body_background"
android:padding="20dp"
android:visibility="gone"
>
<ListView
android:id="#+id/course_adapter_assess_list"
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_marginBottom="10dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:text="More" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:text="New"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
Here is my CourseListAdapter.java file that I use to create each view for each course in the list of courses, minus the usual stuff:
package com.example.schoolplanner2.adapters;
public class CourseListAdapter extends ArrayAdapter<Course> {
private static final String TAG = "CourseListAdapter";
private Context context;
int mResource;
public CourseListAdapter(#NonNull Context context, int resource, #NonNull ArrayList<Course> objects) {
super(context, resource, objects);
this.context = context;
mResource = resource;
}
#NonNull
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
// get info
String course_code = getItem(position).getCourseCode();
Double course_grade = getItem(position).getCurrentGrade();
// make inflater and inflate the layout
LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(mResource, parent, false);
TextView tv_course_code = v.findViewById(R.id.course_adapter_course_code);
TextView tv_course_title = v.findViewById(R.id.course_adapter_course_title);
tv_course_code.setText(course_code);
tv_course_title.setText(String.valueOf(course_grade));
// add on click to each list view element
LinearLayout layout = v.findViewById(R.id.course_adapter_layout);
layout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.i(TAG, "List view element has been clicked " + course_code);
// expand the view to include a new fragment
LinearLayout assess_list_layout = view.findViewById(R.id.assess_list_layout);
assess_list_layout.setVisibility(View.VISIBLE);
// get the list view and add each course to the course view
ListView assessment_list_view = (ListView) view.findViewById(R.id.course_adapter_assess_list);
AssessmentListAdapter assessAdapter = new AssessmentListAdapter(getContext(), R.layout.assessment_adapter_view, getItem(position).getAssessmentList(), getItem(position));
assessment_list_view.setAdapter(assessAdapter);
}
});
return v;
}
}
Please let me know if there is any more information you need. Will also take suggestions on other ways of accomplishing the same thing. Thanks for your help.
~Seth.
Edit: when the assess_list_layout.setVisibility(View.VISIBLE) is outside of the onClick it does work.
Further Edit: Things I have tried so far to no avail:
moving the location of where I define the LinearLayout componenent
calling invalidate() on parent view
using runOnUiThread()
changing view to v in the line where I attempt to findViewById for assess_list_layout, they are the same thing so it does not help.
calling requestLayout() on assess_list_layout
Update: I have now managed to get the assess_list_layout section to appear when the course_adapter_layout is clicked on. The only problem now is that the view does not take up anymore space on the screen, it just turns into a scrollable view that can be scrolled up and down on to see the whole view.
Also, when I scroll to fast, it resets the view back to the way it was on bootup.
1.View Visibility not working
The Visibility is not working because the view is not rendered initially. Remove the visibility gone in the xml and handle the visibility fully in the adapter class. In the 'assess_list_layout' linerlayout height can be hardcode because inside this layout the listview height is already hardcoded. You can hardcode to 300 and check. This way will help the view to get the initial rendering.
2. Scroll issue
While scrolling the already visible 'assess_list_layout' view might be not visible. This is because we need to handle the visibility, this handling is similar to checkbox selection handling in listview. Hope the Course class is model class, in that add a another property named isSelected as boolean and set the default value to false. Please refer below the course class,
Course Class
public class Course {
private boolean isSelected = false;
public boolean isSelected() {
return isSelected;
}
public void setSelected(boolean selected) {
isSelected = selected;
}
}
please refer the below code changes in the adapter class.
public class CourseListAdapter extends ArrayAdapter<Course> {
private static final String TAG = "CourseListAdapter";
private Context context;
int mResource;
public CourseListAdapter(#NonNull Context context, int resource, #NonNull ArrayList<Course> objects) {
super(context, resource, objects);
this.context = context;
mResource = resource;
}
#NonNull
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
// get info
String course_code = getItem(position).getCourseCode();
Double course_grade = getItem(position).getCurrentGrade();
// make inflater and inflate the layout
LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(mResource, parent, false);
TextView tv_course_code = v.findViewById(R.id.course_adapter_course_code);
TextView tv_course_title = v.findViewById(R.id.course_adapter_course_title);
//My Change
// expand the view to include a new fragment
LinearLayout assess_list_layout = view.findViewById(R.id.assess_list_layout);
// get the list view and add each course to the course view
ListView assessment_list_view = (ListView) view.findViewById(R.id.course_adapter_assess_list);
assess_list_layout.setVisibility(View.GONE);
if (getItem().get(position).isSelected()) {
assess_list_layout.setVisibility(View.Visible);
AssessmentListAdapter assessAdapter = new AssessmentListAdapter(getContext(), R.layout.assessment_adapter_view, getItem(position).getAssessmentList(), getItem(position));
assessment_list_view.setAdapter(assessAdapter);
}
//My Change
tv_course_code.setText(course_code);
tv_course_title.setText(String.valueOf(course_grade));
// add on click to each list view element
LinearLayout layout = v.findViewById(R.id.course_adapter_layout);
layout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.i(TAG, "List view element has been clicked " + course_code);
//My Change
getItem().get(position).setSelected(true);
//My Change
assess_list_layout.setVisibility(View.VISIBLE);
AssessmentListAdapter assessAdapter = new AssessmentListAdapter(getContext(), R.layout.assessment_adapter_view, getItem(position).getAssessmentList(), getItem(position));
assessment_list_view.setAdapter(assessAdapter);
}
});
return v;
}
}
I have commented as My Change to find the difference in the code.
issue 1
// make inflater and inflate the layout
LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(mResource, parent, false);
you don't reuse view,which leads to inflate a new view every time when called getView();As inflate() method is IO sensitive,it slow down the smoothness of scrolling ,trigger jank。
try this
// make inflater and inflate the layout
View v = null;
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from(context);
v= inflater.inflate(mResource, parent, false);
} else {
v = convertView;
}
issue 2
when scorll listview ,you need reset itemview state,add a "expand" property to your Course bean ,when click item set expand = true;and then add flowing code above layout.setOnClickListener
v.findViewById(R.id.assess_list_layout).setVisibility( item.expand ? View.VISIBLE:View.GONE);
ListView assessment_list_view = (ListView) v.findViewById(R.id.course_adapter_assess_list);
if (item.expand) {
AssessmentListAdapter assessAdapter = new AssessmentListAdapter(getContext(), R.layout.assessment_adapter_view,
item.getAssessmentList(), item);
assessment_list_view.setAdapter(assessAdapter);
}
issue 3
set setOnClickListener in getView() method , will create a new Clicker instance every time getView() called. use listView.setOnItemClickListener() instead
tips:
after all,you should use RecyclerView instead of ListView,which is a powerful UI widget
I have a page in which I'm taking the START TIME and END TIME from DATABASE.
Let's say the START TIME is 7:00 and END TIME is 22:00
I want to use this START TIME and END TIME to show in my page as textview like 7:00 8:00 9:00 and sooo on till 22:00 as textview
Also I have an imageview that will also increase when the text increases.
How can I achieve this?
Also I want the result text in Horizontal Scroll View with Imageview at top and text view as bottom of each imageview
char first = StartTime.charAt(0);
int StartTimeint = Integer.parseInt(String.valueOf(first));
int l;
for( l = StartTimeint; l<=22; l++){
Log.d("SeatsPage", "Time is "+l);
}
timeofseats.setText(Integer.toString(l));
This is I have done so far but I'm getting 23 as a result, the textview is not increasing
This is my XML File
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:id="#+id/llMain"
android:layout_height="match_parent"
tools:context=".SeatsPagewithDB.SeatsPage">
<ImageView
android:id="#+id/imageView11"
android:layout_width="150px"
android:layout_height="150px"
android:layout_marginStart="28dp"
android:layout_marginEnd="326dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/seat" />
<TextView
android:id="#+id/timeofseats"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="40dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="334dp"
android:background="#FF0000"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:text="7:00"
android:textColor="#fff"
android:textSize="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/imageView11" />
</android.support.constraint.ConstraintLayout>
This is the result I am getting as layout
This what I want programmatically
The XML code that you write in your layout.xml file to create the UI is for static UI only. What you are asking is to create views dynamically during runtime. Although you can definitely create views using java code on a click of a button or something. But it is better to code less for the UI whenever possible and keep it separated from the program code. Instead use the tools given to us by the framework we are using.
In Android those tools include stuff like ListView, GridView and the newer and better RecyclerView. These views help you add other views dynamically to your UI in runtime. You define one of them or more (depending on your UI needs) once in your layout.xml and configure them using java code like any other view.
This is how you can use RecyclerView to achieve your goal. I can't explain everything how RecyclerView works and what each line of code does as it will make a very long post but I have tried to highlight main things briefly.
1. Add RecyclerView in your layout file.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
2. Create another layout file and define the template UI of the item that the RecyclerView is going to display. RecyclerView will populate each item that it holds with this layout.
item_view.xml
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="#+id/imageView_alarm"
android:layout_width="90dp"
android:layout_height="90dp"
android:src="#drawable/alarm" />
<TextView
android:id="#+id/textView_Time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="#FF0000"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:text="Time"
android:textColor="#android:color/background_light"
android:textSize="24sp" />
</LinearLayout>
3. Create a ViewHolder class that extends from RecyclerView.ViewHolder. View holder is a RecyclerView related concept. In short it works as a wrapper around the view of a single item and aids in binding new data to the view of the item. Create a bind() function inside view holder to make your life easier.
EDIT: I have updated the class by implementing the View.OnClickListener interface, modified the constructor to pass in the context from onCreateViewHolder() and adding a setItemPosition() just for the sake to pass the item position number from onBindViewHolder() all over to here so we can use this position number in our onClick() method of the interface
MyViewHolder.java [UPDATED]
public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private TextView textView;
private int itemPosition;
private Context mContext;
public MyViewHolder(#NonNull View itemView, Context context) {
super(itemView);
itemView.setOnClickListener(this);
mContext = context;
textView = itemView.findViewById(R.id.textView_Time);
}
void bind(String timeText)
{
textView.setText(timeText);
}
void setItemPosition(int position)
{
itemPosition = position;
}
#Override
public void onClick(View v) {
Toast.makeText(mContext, "You clicked item number: " + itemPosition , Toast.LENGTH_SHORT).show();
}
}
4. Create an Adapter class that extends from RecyclerView.Adapter. Adapter works as a bridge between the UI data and RecyclerView itself. An Adapter tells the RecyclerView what layout file to inflate and how many to inflate. RecyclerView job is to deal with how to inflate it on the UI.
EDIT : Just changed myViewHolder in onCreateViewHolder() to match the modified constructor of MyViewHolder. Added the call to setItemPosition() in the onBindViewHolder().
MyAdapter.java [UPDATED]
public class MyAdapter extends RecyclerView.Adapter {
List<String> timeIntervalList = new ArrayList<>();
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view, parent, false);
MyViewHolder myViewHolder = new MyViewHolder(view , parent.getContext());
return myViewHolder;
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
MyViewHolder viewHolder = (MyViewHolder) holder;
viewHolder.setItemPosition(position);
viewHolder.bind(timeIntervalList.get(position));
}
#Override
public int getItemCount() {
return timeIntervalList.size();
}
public void addItem (String timeText)
{
timeIntervalList.add(timeText);
notifyItemInserted(getItemCount());
}
}
In this adapter you will see two functions. OnCreateViewHolder() inflates the view using the template layout file for a single item and OnBindViewHolder() binds new data to the default values of the of the view just created. The data used for binding is stored in a list inside this Adapter called the timeIntervalList. This list will hold your time interval strings so they can be updated on the view.
5. Finally, use this RecyclerView where you want to use it. Like in your MainActivity.java. RecyclerView needs to be told in what fashion to display the items (e.g list , grid etc ) using a LayoutManager. LinearLayoutManager will display items either vertically or horizontally. You can see I am using your logic to increment time from string and adding new views to RecyclerView using the addItem() function of the MyAdapter class.
MainActivity.java
public class MainActivity extends AppCompatActivity {
private RecyclerView myRecyclerView;
private MyAdapter myAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myRecyclerView = findViewById(R.id.recyclerView);
myAdapter = new MyAdapter();
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this , LinearLayoutManager.HORIZONTAL, false);
myRecyclerView.setLayoutManager(linearLayoutManager);
myRecyclerView.setAdapter(myAdapter);
// This is how you will populate the recycler view
String START_TIME = "7:00";
String END_TIME = "22:00";
char first = START_TIME.charAt(0);
int StartTimeint = Integer.parseInt(String.valueOf(first));
int l;
for( l = StartTimeint; l<=22; l++){
// This is where new item are added to recyclerView.
myAdapter.addItem(l + ":00");
}
}
}
This is the final result.
Change your activity layout XML code as follows,
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:id="#+id/llMain"
android:layout_height="match_parent"
tools:context=".SeatsPagewithDB.SeatsPage">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
...
...>
<LinearLayout
android:id="#+id/container"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</HorizontalScrollView>
</android.support.constraint.ConstraintLayout>
Move the textview and imageview to another XML file let's call it item_view.xml (you can name it whatever you wish). we are doing so because the root view of this file will be reused.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/imageView11"
android:layout_width="150px"
android:layout_height="150px"
app:srcCompat="#drawable/seat"/>
<TextView
android:id="#+id/timeofseats"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#FF0000"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:text="7:00"
android:textColor="#fff"
android:textSize="20dp"/>
</LinearLayout>
Now make following changes in your Java file
LinearLayout container = findViewById(R.id.container); // or rootView.findViewById() for custom View and Fragment
char first = StartTime.charAt(0);
int StartTimeint = Integer.parseInt(String.valueOf(first));
for(int l = StartTimeint; l<=22; l++){
Log.d("SeatsPage", "Time is "+l);
View view = LayoutInflater.from(container.getContext()).inflate(R.layout.item_view, null);
TextView timeofseats = view.findViewById(R.id.timeofseats);
timeofseats.setText(Integer.toString(l));
container.addView(view);
}
I'm currently working on an android project and I need to launch an activity from an item of a ListView. I searched more or less 8 hours and found always the same solution, which is not working.
Here is my code :
In the onCreate method of CatalogueActivity :
CtlgArrayAdapter adapter = new CtlgArrayAdapter(this, recipeName, recipeDescr, recipeDiff,
recipePic, recipeEval);
ListView list = (ListView)findViewById(android.R.id.list);
list.setAdapter(adapter);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.d(TAG, "Item clicked");
}
});
The CtlgArrayAdapter class :
public class CtlgArrayAdapter extends ArrayAdapter<String> {
private static final String TAG = "CTLGADAPTER";
private final Activity context;
private final String[] recipeName;
private final String[] recipeDescr;
private final Integer[] recipeDiff;
private final Integer[] recipePic;
private final Integer[] recipeEval;
public CtlgArrayAdapter(Activity context, String[] recipeName, String[] recipeDescr,
Integer[] recipeDiff, Integer[] recipePic, Integer[] recipeEval) {
super(context, R.layout.ctlg_list, recipeName);
this.context = context;
this.recipeName = recipeName;
this.recipeDescr = recipeDescr;
this.recipeDiff = recipeDiff;
this.recipePic = recipePic;
this.recipeEval = recipeEval;
}
public View getView(int position, View view, ViewGroup parent) {
LayoutInflater inflater = context.getLayoutInflater();
View rowView = inflater.inflate(R.layout.ctlg_list, null, true);
TextView nameTile = (TextView) rowView.findViewById(R.id.ctlg_list_name);
TextView diffTile = (TextView) rowView.findViewById(R.id.ctlg_list_diff);
TextView descrTile = (TextView) rowView.findViewById(R.id.ctlg_list_descr);
ImageView picTile = (ImageView) rowView.findViewById(R.id.ctlg_list_img);
RatingBar rateTile = (RatingBar) rowView.findViewById(R.id.ctlg_list_rating);
nameTile.setText(recipeName[position]);
diffTile.setText(recipeDiff[position].toString());
descrTile.setText(recipeDescr[position]);
picTile.setImageResource(recipePic[position]);
rateTile.setRating(recipeEval[position]);
return rowView;
}
The problem is that when I tap my item, nothing happens. All the internet seems to say that this the solution, but it doesn't work :'(
Here are my .xml files :
catalogue layout :
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ListView
android:id="#android:id/list"
android:clickable="true"
android:layout_alignParentStart="true"
android:layout_below="#id/linear_ctlg"
android:layout_width="match_parent"
android:layout_height="wrap_content"/> </RelativeLayout>
and the catalogue_item layout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/ctlg_list_img"
android:layout_width="90dp"
android:layout_height="90dp"
android:focusable="false"
android:scaleType="centerCrop"
android:src="#drawable/no_image"
android:layout_marginEnd="10dp"/>
<TextView
android:id="#+id/ctlg_list_name"
android:textColor="#color/colorPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toEndOf="#id/ctlg_list_img"
android:focusable="false"
android:textStyle="bold"
android:text="Nom de la recette sur son lit de char"/>
<TextView
android:id="#+id/ctlg_list_diff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="#id/ctlg_list_img"
android:layout_below="#id/ctlg_list_name"
android:text="Difficile"/>
<TextView
android:id="#+id/ctlg_list_descr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/ctlg_list_diff"
android:layout_toEndOf="#id/ctlg_list_img"
android:focusable="false"
android:textStyle="italic"
android:text="Ceci est la description de la recette. J'en mets une longue pour être sûr qu'il y ait assez d'espace. Il fait beau aujourd'hui."/>
<RatingBar
android:id="#+id/ctlg_list_rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:focusable="false"
android:scaleX="0.4"
android:scaleY="0.4"
android:numStars="5"
android:rating="3"
android:stepSize="1"/> </RelativeLayout>
Thank you sooooooo much if you find what's wrong with my code ! :)
Make sure none of your views in catelogue_item are focusable or clickable, use the code below.
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
Also try with setting the root layout with: android:descendantFocusability="blocksDescendants"
The reason is if any of your element in the list contains focusable item , the onclick of list will not be called.
I'm not sure whether its right solution.
Your passing activity instead of context here
super(context, R.layout.ctlg_list, recipeName); , so try to pass context.
Check this post which is similar to code it might be useful.
You dont need any focusable:false or clickable:false . you can remove all of them. just add this code android:descendantFocusability="blocksDescendants" in your catalogue xml top relative layout . I tried it with your code and worked.
I found a solution :
I suppressed the rating bar from the catalogue_tiem layout and now everything works perfectly (I don't need to keep it in this layout because it will be displayed later). I suppose that there are solutions to make it work with the rating bar, by modifying attributes in the xml file, but... nevermind :)
What I am trying to accomplish is to have a checkbox in each row, having the ability to check the box separately (for batch deleting) and being able to select the entire row to view data associated with the list item.
I have done a checkbox with a textview but that only lets be select the box and I cant click on the list item to view that items data. I also used checkedTextView but that checks the box where ever you click on the row calling the onListItemClick and thats not what I want either. Is there some what I can separate checking the box from clicking a listview item?
Pretty much trying to do what the gmail app does when selecting messages to delete and view
this is my row layout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<CheckBox
android:id="#+id/checkBox1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"/>
<TextView
android:id="#+id/nameCheckTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="#+id/checkBox1"
android:layout_toRightOf="#+id/checkBox1"
android:paddingTop="5dp"
android:paddingLeft="15dp"
android:text="Name"
android:textAppearance="?android:attr/textAppearanceLarge" />
</RelativeLayout>
creating listview
#Override
public void onActivityCreated(Bundle state){
super.onActivityCreated(state);
lv = getListView();
lv.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
lv.setItemsCanFocus(false);
setEmptyText("No Bowlers");
registerForContextMenu(getListView());
populateList();
}
EDIT:
my populate method
public void populateList(){
String[] fields = new String[] {BowlersDB.NAME};
//mAdapter = new CheckAdapter(getActivity(),R.layout.check_listview,null,fields,new int[] {R.id.nameCheckTV});
mAdapter = new SimpleCursorAdapter(getActivity(),R.layout.check_listview,null,fields,
new int[] {R.id.nameCheckTV});
setListAdapter(mAdapter);
getLoaderManager().initLoader(0,null,this);
}
The issue is that Android doesn't allow you to select list items that have elements on them that are focusable. Try modifying the checkbox on the list item:
android:focusable="false"
I had a strange workaround with this issue. Here is my solution.
Use this for your ListView. Product is just a model object to hold your data in this case:
public class CatalogItemAdapter extends ArrayAdapter<Product> //
{
private ArrayList<Product> products;
private Activity activity;
public CatalogItemAdapter(Context context, int textViewResourceId,
ArrayList<Product> items, Activity activity) //
{
super(context, textViewResourceId, items);
this.products = items;
this.activity = activity;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) //
{
Product product = products.get(position);
if (convertView == null) //
{
LayoutInflater vi = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.catalog_item_stub, null, false);
}
}
}
Somewhere in your onResume(), put this:
listView = (ListView) activity.findViewById(R.id.CatalogProducts);
m_adapter = new CatalogItemAdapter(activity,
R.layout.catalog_item_stub, products, activity);
if (products == null)
products = new ArrayList<Product>();
listView.setAdapter(m_adapter);
R.layout.catalog_item_stub is a layout stub created in XML like so (put the appropriate items in it for you, like the checkbox):
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout android:id="#+id/catalog_item_stub"
android:layout_width="match_parent" android:layout_height="90dp"
xmlns:android="http://schemas.android.com/apk/res/android"
android:padding="5dp">
<LinearLayout android:layout_height="match_parent"
android:layout_width="match_parent" android:weightSum="5"
android:gravity="center_vertical|right">
<TextView android:layout_width="0dp" android:layout_height="match_parent"
android:text="product_title" android:textAppearance="?android:attr/textAppearanceMedium"
android:id="#+id/ProductTitle" android:padding="5dp"
android:layout_weight="2.5" android:textColor="#000000" />
<CheckBox android:padding="5dp" android:layout_height="match_parent"
android:text="select" android:layout_width="0dp" android:id="#+id/chkSelect"
android:layout_weight="1.5" android:textColor="#000000"
android:gravity="right" />
</LinearLayout>
Hopefully this helps! Holler if you need any clarification.
The gmail app uses its own view called CanvasConversationHeaderView that manages its subviews. This method is probably more heavy-weight than what you are looking for.
An easier method would be to make the checkbox not "focusable" (as Alex Lockwood suggests) and then attach an onClick in the XML.
<CheckBox
android:id="#+id/checkBox1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:focusable="false"
android:onClick="onCheckboxClicked"/>
Then in your activity code add
public void onCheckboxClicked(View view) {
RelativeLayout rl = (RelativeLayout)view.getParent();
Log.d(TAG, "Checkbox clicked! getTag returned: " + rl.getTag());
}
EDIT: How to add a tag from SimpleCursorAdapter.bindView
private class MyCursorAdapter extends SimpleCursorAdapter {
public MyCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
super(context, layout, c, from, to);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
//Log.d(TAG, "Cursor pos: " + cursor.getPosition());
String name = cursor.getString(
cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
view.setTag(name);
super.bindView(view, context, cursor);
}
}
Note: I set the tag to the View from the bindView call, which is the RelativeLayout at the root of your xml. Look at the onCheckboxClicked method to see how I got the tag.
You need to set an onItemClickListener for your ListView that will start another activity with the info of the row selected when the row is clicked (outside the CheckBox of course). I would recommend having your Activity implement AdapterView.OnItemClickListener which requires the method
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {}
Inside this method you can launch an Activity with details corresponding to the data in the row selected.
Hopefully I understood your question correctly.