I have this RelativeLayout which expand and collapse on button click
it works fine on one button.
I want to reuse same method on more two RelativeLayout
in same layout
and expand using other two buttons.
This code is running fine. just want more layout to do same action.
Layout:
This is my code:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="64dp"
android:background="#FFF"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Title"
android:textSize="20sp" />
<Button
android:id="#+id/viewmore"
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_marginLeft="280dp"
android:background="#null"
android:text="viewmore" />
</RelativeLayout>
<RelativeLayout
android:visibility="gone"
android:id="#+id/expandable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:background="#color/colorAccent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="133dp"
android:text="Text messaging, or texting, is the act of composing and sending electronic messages, typically consisting of alphabetic and numeric characters"
android:textSize="20sp" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/textView4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Title 2"
android:textSize="20sp" />
<Button
android:id="#+id/viewmore1"
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_marginLeft="280dp"
android:background="#null"
android:text="viewmore" />
</RelativeLayout>
<RelativeLayout
android:visibility="gone"
android:animateLayoutChanges="true"
android:id="#+id/expandable1"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="30dp"
android:background="#color/colorPrimary">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Text messaging, or texting, is the act of composing and sending electronic messages, typically consisting of alphabetic and numeric characters"
android:textSize="20sp" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Title 3"
android:textSize="20sp" />
<Button
android:id="#+id/viewmore2"
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_marginLeft="280dp"
android:background="#null"
android:text="viewmore" />
</RelativeLayout>
<RelativeLayout
android:visibility="gone"
android:animateLayoutChanges="true"
android:id="#+id/expandable2"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="30dp"
android:background="#color/colorPrimary">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Text messaging, or texting, is the act of composing and sending electronic messages, typically consisting of alphabetic and numeric characters"
android:textSize="20sp" />
</RelativeLayout>
</LinearLayout>
</ScrollView>
Source Code:
RelativeLayout relativeLayout, relativeLayout1, relativeLayout2;
Button viewmore, viewmore1, viewmore2;
ValueAnimator mAnimator;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.viewmore);
relativeLayout = (RelativeLayout) findViewById(R.id.expandable);
relativeLayout1 = (RelativeLayout) findViewById(R.id.expandable1);
relativeLayout2 = (RelativeLayout) findViewById(R.id.expandable2);
viewmore = (Button) findViewById(R.id.viewmore);
viewmore1 = (Button) findViewById(R.id.viewmore1);
viewmore2 = (Button) findViewById(R.id.viewmore2);
viewmore.setOnClickListener(this);
viewmore1.setOnClickListener(this);
viewmore2.setOnClickListener(this);
relativeLayout.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
relativeLayout.getViewTreeObserver().removeOnPreDrawListener(this);
relativeLayout.setVisibility(View.GONE);
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
relativeLayout.measure(widthSpec, heightSpec);
mAnimator = slideAnimator(0, relativeLayout.getMeasuredHeight());
return true;
}
});
}
private void expand() {
relativeLayout.setVisibility(View.VISIBLE);
mAnimator.start();
}
private void collapse() {
int finalHeight = relativeLayout.getHeight();
ValueAnimator mAnimator = slideAnimator(finalHeight, 0);
mAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animator) {
//Height=0, but it set visibility to GONE
relativeLayout.setVisibility(View.GONE);
}
#Override
public void onAnimationStart(Animator animator) {
}
#Override
public void onAnimationCancel(Animator animator) {
}
#Override
public void onAnimationRepeat(Animator animator) {
}
});
mAnimator.start();
}
private ValueAnimator slideAnimator(int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//Update Height
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = relativeLayout.getLayoutParams();
layoutParams.height = value;
relativeLayout.setLayoutParams(layoutParams);
}
});
return animator;
}
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.viewmore:
if (relativeLayout.getVisibility() == View.GONE) {
expand();
} else {
collapse();
}
break;
case R.id.viewmore1:
break;
case R.id.viewmore2:
break;
}
}
To continue with your approach, you will have to make the code apply to all three sections that you have laid out. To do this, you will need to change several of your methods to accept a RelativeLayout as an argument.
First, in your onClick listener, fill in the case blocks so each block calls expand() with the targeted RelativeLayout and maximum height. Call collapse() with the targeted RelativeLayout. You will then need to modify expand() and collapse() to handle the new arguments:
You will notice in the following code that I have changed how and where the animator is created. The animator will need to work with each RelativeLayout.
So, onClick() calls expand() which calls slideAnimator(). For each call, the RelativeLayout that is effected is passed as an argument. In this way, you can generalize the code to work with more than one RelativeLayout.
The pre-draw listener will also need to measure each expandable RelativeLayout.
Here is it all put together:
MainActivity.xml
public class MainActivity extends AppCompatActivity
implements View.OnClickListener {
RelativeLayout relativeLayout, relativeLayout1, relativeLayout2;
Button viewmore, viewmore1, viewmore2;
int height, height1, height2;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.viewmore);
relativeLayout = (RelativeLayout) findViewById(R.id.expandable);
relativeLayout1 = (RelativeLayout) findViewById(R.id.expandable1);
relativeLayout2 = (RelativeLayout) findViewById(R.id.expandable2);
viewmore = (Button) findViewById(R.id.viewmore);
viewmore1 = (Button) findViewById(R.id.viewmore1);
viewmore2 = (Button) findViewById(R.id.viewmore2);
viewmore.setOnClickListener(this);
viewmore1.setOnClickListener(this);
viewmore2.setOnClickListener(this);
relativeLayout.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
relativeLayout.getViewTreeObserver().removeOnPreDrawListener(this);
relativeLayout.setVisibility(View.GONE);
relativeLayout1.setVisibility(View.GONE);
relativeLayout2.setVisibility(View.GONE);
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
relativeLayout.measure(widthSpec, heightSpec);
height = relativeLayout.getMeasuredHeight();
relativeLayout1.measure(widthSpec, heightSpec);
height1 = relativeLayout.getMeasuredHeight();
relativeLayout2.measure(widthSpec, heightSpec);
height2 = relativeLayout.getMeasuredHeight();
return true;
}
});
}
private void expand(RelativeLayout layout, int layoutHeight) {
layout.setVisibility(View.VISIBLE);
ValueAnimator animator = slideAnimator(layout, 0, layoutHeight);
animator.start();
}
private void collapse(final RelativeLayout layout) {
int finalHeight = layout.getHeight();
ValueAnimator mAnimator = slideAnimator(layout, finalHeight, 0);
mAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animator) {
//Height=0, but it set visibility to GONE
layout.setVisibility(View.GONE);
}
#Override
public void onAnimationStart(Animator animator) {
}
#Override
public void onAnimationCancel(Animator animator) {
}
#Override
public void onAnimationRepeat(Animator animator) {
}
});
mAnimator.start();
}
private ValueAnimator slideAnimator(final RelativeLayout layout, int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//Update Height
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = layout.getLayoutParams();
layoutParams.height = value;
layout.setLayoutParams(layoutParams);
}
});
return animator;
}
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.viewmore:
if (relativeLayout.getVisibility() == View.GONE) {
expand(relativeLayout, height);
} else {
collapse(relativeLayout);
}
break;
case R.id.viewmore1:
if (relativeLayout1.getVisibility() == View.GONE) {
expand(relativeLayout1, height1);
} else {
collapse(relativeLayout1);
}
break;
case R.id.viewmore2:
if (relativeLayout2.getVisibility() == View.GONE) {
expand(relativeLayout2, height2);
} else {
collapse(relativeLayout2);
}
break;
}
}
}
You can also create own custom expandable which extend android relative layout. On that custom view you can store expanded or collapsed status. As well as you can create custom attributes for define your view default status like expanded or collapsed. So you don't need to compare view status you will just call your toggle function which toggle your view expanded to collapse or vice versa
If you want to show collapsed view as a default view you should not change view visibility before onMeasure function and store your view measured height. If you change visibility on view constructor onMeasure function skip calculation for that view. You should toggle visibility on onPreDraw function.
Related
I have a ConstraintLayout with a TextView and EditText inside. The TextView is on the left, and when the EditText gains focus, I want the TextView to change its TextSize and move to the right. When it loses focus, I want to reverse that.
This is the Layout:
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/custom_edit_text_constraint_layout"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<EditText
android:id="#+id/custom_edit_text_text_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:paddingLeft="#dimen/custom_edittext_def_hint_margin"
android:paddingStart="#dimen/custom_edittext_def_hint_margin"
tools:ignore="RtlSymmetry"
/>
<TextView
android:id="#+id/custom_edit_text_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="#dimen/custom_edittext_def_hint_margin"
android:layout_marginStart="#dimen/custom_edittext_def_hint_margin"/>
</androidx.constraintlayout.widget.ConstraintLayout>
And this is the code (If I forgot any important parts I can edit it):
hint = findViewById(R.id.custom_edit_text_hint); //TextView
textField = findViewById(R.id.custom_edit_text_text_field); //EditText
textField.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if(hasFocus) {
focusHint();
} else {
unfocusHint();
}
}
});
private void focusHint() {
hint.setTextSize(hintSizeFocused);
hint.setTextColor(hintColorFocused);
moveHintToRight();
}
private void unfocusHint() {
hint.setTextColor(hintColor);
if(textField.getText().toString().isEmpty()) {
hint.setTextSize(hintSize);
moveHintToLeft();
}
}
private void moveHintToRight() {
int horizontalDistance = textField.getWidth() - hint.getRight() - dpToPx(HINT_MARGIN_SIDE);
TranslateAnimation anim = new TranslateAnimation(0, horizontalDistance, 0, 0);
anim.setDuration(ANIMATION_DURATION);
anim.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {}
#Override
public void onAnimationEnd(Animation animation) {
ConstraintLayout l = findViewById(R.id.custom_edit_text_constraint_layout);
ConstraintSet set = new ConstraintSet();
set.clone(l);
set.clear(R.id.custom_edit_text_hint, ConstraintSet.LEFT);
set.connect(R.id.custom_edit_text_hint, ConstraintSet.RIGHT, R.id.custom_edit_text_constraint_layout, ConstraintSet.RIGHT, dpToPx(HINT_MARGIN_SIDE));
set.applyTo(l);
}
#Override
public void onAnimationRepeat(Animation animation) {}
});
hint.startAnimation(anim);
}
private void moveHintToLeft() {
int horizontalDistance = - hint.getLeft() + dpToPx(HINT_MARGIN_SIDE);
TranslateAnimation anim = new TranslateAnimation(0, horizontalDistance, 0, 0);
anim.setDuration(ANIMATION_DURATION);
anim.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {}
#Override
public void onAnimationEnd(Animation animation) {
ConstraintSet set = new ConstraintSet();
ConstraintLayout l = findViewById(R.id.custom_edit_text_constraint_layout);
set.clone(l);
set.clear(R.id.custom_edit_text_hint, ConstraintSet.RIGHT);
set.connect(R.id.custom_edit_text_hint, ConstraintSet.LEFT, R.id.custom_edit_text_constraint_layout, ConstraintSet.LEFT, dpToPx(HINT_MARGIN_SIDE));
set.applyTo(l);
}
#Override
public void onAnimationRepeat(Animation animation) {}
});
hint.startAnimation(anim);
}
This works great, but only when I don't resize the TextSize of the TextView. When I resize the TextSize (as shown in the code), hint.getLeft() and hint.getRight() return the values, which the TextView would have with the old TextSize, and this results in that the TextView moves either too far or not far enought. But this doesn't make sense to me because I resize the TextSize BEFORE I start the animation and the TextView's width is set to wrap_content. Does anyone have an idea why this doesn't work and how I can fix it?
EDIT:
To further explain and simplify what exactly the problem is, I have an example:
textView.setTextSize(12);
int width1 = hint.getWidth();
textView.setTextSize(18);
int width2 = hint.getWidth();
As the TextView's width is set to wrap_content, the width should change when I change the textSize (at least I thought so). But width1 and width2 are the same. How can I fix that?
EDIT2:
I solved the problem with
this answer by Eric.
Add right and end constraint of the textview as follows.If you want it to be in middle then set horizontal_bias = 0.5 or if you want it left then 0.0 and lastly in right 1.0
<TextView
android:id="#+id/custom_edit_text_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="#dimen/custom_edittext_def_hint_margin"
android:layout_marginStart="#dimen/custom_edittext_def_hint_margin"
android:layout_constraintRight_toRightOf = "parent"
android:layout_constraintEnd_toEndOf = "parent"
app:layout_constraintHorizontal_bias="0.0"
/>
Hope this will work.Try it
I'm working on a quiz app where there is one question and five radio buttons per CardView in a RecyclerView. Each radio button has an assigned score.
For Example, there are two CardViews
CardView #1
radio_button_1 = -20
radio_button_2 = -10
radio_button_3 = 0
radio_button_4 = 10
radio_button_5 = 20
CardView #2
radio_button_1 = -20
radio_button_2 = -10
radio_button_3 = 0
radio_button_4 = 10
radio_button_5 = 20
Say user picks radio_button_1 on CardView #1 and he picks radio_button_3 on CardView #2 and so forth. I want to add the points between CardViews and be able to preserve/ save those points as user scrolls through the CardViews. I don't know how it works with RecyclerView. SharedPreferences?
I have added a text view to test and see if the score is being updated in the background with the new score.
Recycler Adapter:
public class MainAdapter extends RecyclerView.Adapter<MainAdapter.ViewHolder> {
private List<App> mApps;
public static int score;
public static int updateScore() {
return score;
}
public MainAdapter(List<App> apps) {
mApps = apps;
}
#Override
public MainAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.cards_adapter, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
#Override
public int getItemViewType(int position) {
return (position);
}
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
App app = mApps.get(position);
holder.questionTextView.setText(app.getQuestion());
}
#Override
public int getItemCount() {
return mApps.size();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private TextView questionTextView;
private TextView titleTextView;
public RadioGroup radioGroup;
public RadioButton rb1, rb2, rb3, rb4, rb5;
public ViewHolder(View itemView) {
super(itemView);
radioGroup = itemView.findViewById(R.id.radio_group);
radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
int selectedValue = 0;
switch (checkedId) {
case R.id.radio_button_1:
selectedValue -= 20;
break;
case R.id.radio_button_2:
selectedValue -= 10;
break;
case R.id.radio_button_3:
selectedValue += 0;
break;
case R.id.radio_button_4:
selectedValue += 10;
break;
case R.id.radio_button_5:
selectedValue += 20;
break;
}
updateValue (selectedValue);
}
public int updateValue(int selectedValue) {
/**Only to test if value is being tallied**/
TextView valueView = titleTextView.findViewById(R.id.title);
valueView.setText(String.valueOf(selectedValue));
return selectedValue;
}
});
questionTextView = itemView.findViewById(R.id.question);
titleTextView = itemView.findViewById(R.id.title);
rb1 = itemView.findViewById(R.id.radio_button_1);
rb2 = itemView.findViewById(R.id.radio_button_2);
rb3 = itemView.findViewById(R.id.radio_button_3);
rb4 = itemView.findViewById(R.id.radio_button_4);
rb5 = itemView.findViewById(R.id.radio_button_5);
}
#Override
public void onClick(View v) {
Log.d("App", mApps.get(getAdapterPosition()).getQuestion());
}
}}
MainActivity:
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private RecyclerView.LayoutManager mLayoutManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = findViewById(R.id.recycler_view);
SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(mRecyclerView);
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
mRecyclerView.setLayoutManager(mLayoutManager);
setupMainAdapter();
}
private void setupMainAdapter() {
List<App> apps = getApps();
MainAdapter adapter = new MainAdapter(apps);
mRecyclerView.setAdapter(adapter);
}
private List<App> getApps() {
List<App> apps = new ArrayList<>();
apps.add(new App((getResources().getString(R.string.question_1))));
apps.add(new App((getResources().getString(R.string.question_2))));
apps.add(new App((getResources().getString(R.string.question_3))));
apps.add(new App((getResources().getString(R.string.question_4))));
apps.add(new App((getResources().getString(R.string.question_5))));
apps.add(new App((getResources().getString(R.string.question_6))));
apps.add(new App((getResources().getString(R.string.question_7))));
apps.add(new App((getResources().getString(R.string.question_8))));
apps.add(new App((getResources().getString(R.string.question_9))));
apps.add(new App((getResources().getString(R.string.question_10))));
return apps;
}
}
CardView XML
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="275dp"
android:layout_height="575dp"
android:layout_margin="16dp"
card_view:cardBackgroundColor="#color/colorAccent"
card_view:cardCornerRadius="0dp"
card_view:cardElevation="8dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:fontFamily="#font/futura_medium"
android:padding="24dp"
android:textColor="#000000"
android:textSize="32sp"
tools:text="#string/title" />
<TextView
android:id="#+id/question"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/title"
android:layout_centerHorizontal="true"
android:fontFamily="#font/futura_medium"
android:textColor="#000000"
android:textSize="18sp"
tools:text="#string/question" />
<RadioGroup
android:id="#+id/radio_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/question"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_margin="16dp"
android:orientation="horizontal">
<RadioButton
android:id="#+id/radio_button_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:buttonTint="#color/colorPrimaryDark"
android:text="-2"
android:textColor="#000000" />
<RadioButton
android:id="#+id/radio_button_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:buttonTint="#color/colorPrimaryDark"
android:text="-1"
android:textColor="#000000" />
<RadioButton
android:id="#+id/radio_button_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:buttonTint="#color/colorPrimaryDark"
android:text="0"
android:textColor="#000000" />
<RadioButton
android:id="#+id/radio_button_4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:buttonTint="#color/colorPrimaryDark"
android:text="1"
android:textColor="#000000" />
<RadioButton
android:id="#+id/radio_button_5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:buttonTint="#color/colorPrimaryDark"
android:text="2"
android:textColor="#000000" />
</RadioGroup>
<com.google.android.material.button.MaterialButton
android:id="#+id/submit_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_margin="16dp"
android:layout_marginBottom="32dp"
android:fontFamily="#font/futura_medium"
android:text="#string/submit"
android:textColor="#color/colorAccent"
android:textSize="16sp"
app:backgroundTint="#color/colorPrimaryDark"
app:rippleColor="#color/colorPrimary" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
You can declare an interface inside MainAdapter, lets call it ScoreUpdatesListener. It has one method that will be called whenever you want to update the textView.
You will need to implement that interface in your MainActivity, you may do it anonymously, or have the class itself implements that interface. like class MainActivity implements MainAdapter.ScoreUpdatesListener {}.
Either way, you need to pass that interface implementation to the adapter and save its reference. You may do it in the constructor, or add a set method to the adapter.
Now, when updating viewHolder's value, call that listener and it will update the textView from the activity.
class MainAdaptaer {
private ScoreUpdates listener;
...
class ViewHolder... {
...
radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
int selectedValue = 0;
switch (checkedId) {
case R.id.radio_button_1:
selectedValue -= 20;
break;
case R.id.radio_button_2:
selectedValue -= 10;
break;
case R.id.radio_button_3:
selectedValue += 0;
break;
case R.id.radio_button_4:
selectedValue += 10;
break;
case R.id.radio_button_5:
selectedValue += 20;
break;
}
score += selectedValue;
// score is an existing attribute of MainAdapter
// it can and should be non static attribute
listener.updateScore(score);
}
}
interface ScoreUpdatesListener {
void onScoreUpdate(int score);
}
}
In your activity:
private void setupMainAdapter() {
List<App> apps = getApps();
MainAdapter adapter = new MainAdapter(apps, new MainAdapter.ScoreUpdatesListener() {
#Override
public void onScoreUpdate(int score) {
TextView valueView = titleTextView.findViewById(R.id.title);
valueView.setText(String.valueOf(score));
}
});
mRecyclerView.setAdapter(adapter);
}
If the class itself implemented the interface, just pass this to the adapter.
You don't have to move the TextView handling to the activity, but you should. The adapter shouldn't mess with views that isn't ViewHolders
I'm trying to show an additional CardView that contains a different layout but have got lost with my code. I'm also unsure of how to to show the array of strings within a GridView for the CardView itself. Does anyone know where I may have gone wrong & what can be done so that the following can be achieved?:
Place the CardView containing the GridView wherever I want within the RecyclerView.
Show the array of strings in a GridView within a CardView
What I want to add to the RecyclerView (above the Item A CardView)
RecyclerView current contents
Fragment class
public class MyFragment extends android.support.v4.app.Fragment {
private MonRecyclerAdapterWithGrid adapterG;
static final String[] frenchVowels = new String[]{
"a", "e", "i", "o", "u", "y"
};
public MyFragment() {}
#Override
public View onCreateView(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;
recyclerView = v.findViewById(R.id.my_recyclerview);
linearLayoutManager = new LinearLayoutManager(getActivity());
MyRecyclerAdapter adapter = new MyRecyclerAdapter(getContext(), getHeader(), getListItemsG(), getListItemsT());
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(adapter);
super.onActivityCreated(savedInstanceState);
}
RecyclerView recyclerView;
LinearLayoutManager linearLayoutManager;
public RecyclerViewHeader getHeader()
{
return new RecyclerViewHeader();
}
public List<RecyclerViewItemGV> getListItemsG() {
List<RecyclerViewItemGV> rvItemsG = new ArrayList<>();
RecyclerViewItemGV itemG = new RecyclerViewItemGV();
itemG.setTitleGV("Item A");
itemG.setVowelsGV(adapterG);
for (String fVowels : frenchVowels) {
// ?????? Still not working :-(
adapterG.addAdapterItem(new MyFragment.AdapterItem(frenchVowels));
}
rvItemsG.add(itemG);
return rvItemsG;
}
public List<RecyclerViewItemTV> getListItemsT()
{
List<RecyclerViewItemTV> rvItemsT = new ArrayList<>();
RecyclerViewItemTV itemA = new RecyclerViewItemTV();
itemA.setTitleTV("Item A");
itemA.setDescriptionTV("Feature A1");
rvItemsT.add(itemA);
RecyclerViewItemTV itemB = new RecyclerViewItemTV();
itemB.setTitleTV("Item B");
itemB.setDescriptionTV("Feature B1\nFeature B2");
rvItemsT.add(itemB);
RecyclerViewItemTV itemC = new RecyclerViewItemTV();
itemC.setTitleTV("Item C");
itemC.setDescriptionTV("Feature C1\nFeature C2\nFeature C3");
rvItemsT.add(itemC);
return rvItemsT;
}
}
RecyclerView adapter class
public class MyRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEMG = 1;
private static final int TYPE_ITEMT = 2;
private Context mContext;
RecyclerViewHeader header;
List<RecyclerViewItemGV> listItemsG;
List<RecyclerViewItemTV> listItemsT;
ValueAnimator mAnimator;
public MyRecyclerAdapter(Context context, RecyclerViewHeader header, List<RecyclerViewItemGV> listItemsG, List<RecyclerViewItemTV> listItemsT)
{
this.mContext = context;
this.header = header;
this.listItemsG = listItemsG;
this.listItemsT = listItemsT;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType == TYPE_HEADER)
{
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_header_expandcollapsebuttons, parent, false);
return new MyRecyclerAdapter.VHHeader(v);
}
else if(viewType == TYPE_ITEMG)
{
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_item_gv, parent, false);
return new MyRecyclerAdapter.VHItemG(v);
}
else if(viewType == TYPE_ITEMT)
{
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_item_tv, parent, false);
return new MyRecyclerAdapter.VHItemT(v);
}
throw new RuntimeException("there is no type that matches the type " + viewType + " + make sure your using types correctly");
}
private RecyclerViewItemGV getItemG(int position)
{
return listItemsG.get(position);
}
private RecyclerViewItemTV getItemT(int position)
{
return listItemsT.get(position);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final Typeface iconFont = FontManager.getTypeface(mContext, FontManager.FONTAWESOME);
if (holder instanceof MyRecyclerAdapter.VHHeader)
{
final MyRecyclerAdapter.VHHeader vhHeader = (MyRecyclerAdapter.VHHeader)holder;
}
else if (holder instanceof MyRecyclerAdapter.VHItemG){
RecyclerViewItemGV currentItemG = getItemG(position-1);
final MonRecyclerAdapterWithGrid.VHItemG vhItemG = (MyRecyclerAdapter.VHItemG)holder;
vhItemG.txtAG.setText(currentItemG.getTitleGV());
vhItemG.mGridViewG.setVisibility(View.GONE);
vhItemG.txtExpandCollapseG.setText(R.string.fa_icon_chevron_down);
vhItemG.txtExpandCollapseG.setTypeface(iconFont);
//Add onPreDrawListener
vhItemG.mGridViewG.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
vhItemG.mGridViewG.getViewTreeObserver().removeOnPreDrawListener(this);
vhItemG.mGridViewG.setVisibility(View.GONE);
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
vhItemG.mGridViewG.measure(widthSpec, heightSpec);
vhItemG.mGridViewHeight = vhItemG.mGridViewG.getMeasuredHeight();
return true;
}
});
vhItemG.mCardViewG.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(vhItemG.mGridViewG.getVisibility() == View.GONE){
vhItemG.expandG();
} else {
vhItemG.collapseG();
}
}
});
}
else if (holder instanceof MyRecyclerAdapter.VHItemT)
{
RecyclerViewItemTV currentItem = getItemT(position-2);
final MyRecyclerAdapter.VHItemT vhItemT = (MyRecyclerAdapter.VHItemT)holder;
vhItemT.txtA.setText(currentItem.getTitleTV());
vhItemT.txtB.setText(currentItem.getDescriptionTV());
vhItemT.txtB.setVisibility(View.GONE);
vhItemT.txtExpandCollapse.setText(R.string.fa_icon_chevron_down);
vhItemT.txtExpandCollapse.setTypeface(iconFont);
//Add onPreDrawListener
vhItemT.txtB.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
vhItemT.txtB.getViewTreeObserver().removeOnPreDrawListener(this);
vhItemT.txtB.setVisibility(View.GONE);
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
vhItemT.txtB.measure(widthSpec, heightSpec);
vhItemT.textBHeight = vhItemT.txtB.getMeasuredHeight();
return true;
}
});
vhItemT.cardView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(vhItemT.txtB.getVisibility() == View.GONE){
vhItemT.expandT();
} else {
vhItemT.collapseT();
}
}
});
}
}
// need to override this method
#Override
public int getItemViewType(int position) {
if(isPositionHeader(position))
return TYPE_HEADER;
return TYPE_ITEMG;
return TYPE_ITEMT;
}
private boolean isPositionHeader(int position)
{
return position == 0;
}
#Override
public int getItemCount() {
return listItemsG.size()+1;
return listItemsT.size()+1;
}
class VHHeader extends RecyclerView.ViewHolder{
Button btnCollapseAll, btnExpandAll;
public VHHeader(View headerView) {
super(headerView);
this.btnCollapseAll = headerView.findViewById(R.id.btn_collapseall);
this.btnExpandAll = headerView.findViewById(R.id.btn_expandall);
}
}
public class VHItemG extends RecyclerView.ViewHolder{
CardView mCardViewG;
LinearLayout mLinearLayoutG;
RelativeLayout mRelativeLayoutG;
RecyclerView mRecyclerViewG;
GridView mGridViewG;
TextView txtExpandCollapseG, txtAG;
public int mGridViewHeight;
public VHItemG(View itemView) {
super(itemView);
this.mCardViewG = itemView.findViewById(R.id.cv_gv);
this.mLinearLayoutG = itemView.findViewById(R.id.linearlayout_gv_titlerow);
this.mRelativeLayoutG = itemView.findViewById(R.id.relativelayout_gv);
this.mRecyclerViewG = itemView.findViewById(R.id.my_recyclerview);
this.txtAG = itemView.findViewById(R.id.tv_gv_A);
this.txtExpandCollapseG = itemView.findViewById(R.id.tv_gv_expandcollapse);
this.mGridViewG = itemView.findViewById(R.id.gv_a);
}
private void expandG() {
// change visibility to 'VISIBLE'
mGridViewG.setVisibility(View.VISIBLE);
// change direction of chevron to 'up'
txtExpandCollapseG.setText(R.string.fa_icon_chevron_up);
// apply animation to the height of 'txtB'
mAnimator = slideAnimator(0, mGridViewHeight);
// start the animation
mAnimator.start();
}
private void collapseG() {
// change direction of chevron to 'down'
txtExpandCollapseG.setText(R.string.fa_icon_chevron_down);
int finalHeight = mGridViewG.getHeight();
ValueAnimator mAnimator = slideAnimator(finalHeight, 0);
mAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animator) {
// Height will be 0, but set visibility to 'GONE'
mGridViewG.setVisibility(View.GONE);
}
#Override
public void onAnimationStart(Animator animator) {
}
#Override
public void onAnimationCancel(Animator animator) {
}
#Override
public void onAnimationRepeat(Animator animator) {
}
});
mAnimator.start();
}
public ValueAnimator slideAnimator(int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
// update height
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = mGridViewG.getLayoutParams();
layoutParams.height = value;
mGridViewG.setLayoutParams(layoutParams);
}
});
return animator;
}
}
public class VHItemT extends RecyclerView.ViewHolder{
CardView cardView;
LinearLayout mLinearLayout;
RecyclerView mRecyclerView;
RelativeLayout mRelativeLayout;
TextView txtExpandCollapse, txtA, txtB;
public int textBHeight;
public VHItemT(View itemView) {
super(itemView);
this.cardView = itemView.findViewById(R.id.linearlayout_tv_main);
this.mLinearLayout = itemView.findViewById(R.id.linearlayout_tv_titlerow);
this.mRelativeLayout = itemView.findViewById(R.id.relativelayout_tv);
this.mRecyclerView = itemView.findViewById(R.id.my_recyclerview);
this.txtExpandCollapse = itemView.findViewById(R.id.tv_tv_expandcollapse);
this.txtA = itemView.findViewById(R.id.tv_tv_A);
this.txtB = itemView.findViewById(R.id.tv_tv_B);
}
private void expandT() {
// change visibility to 'VISIBLE'
txtB.setVisibility(View.VISIBLE);
// change direction of chevron to 'up'
txtExpandCollapse.setText(R.string.fa_icon_chevron_up);
// apply animation to the height of 'txtB'
mAnimator = slideAnimator(0, textBHeight);
// start the animation
mAnimator.start();
}
private void collapseT() {
// change direction of chevron to 'down'
txtExpandCollapse.setText(R.string.fa_icon_chevron_down);
int finalHeight = txtB.getHeight();
ValueAnimator mAnimator = slideAnimator(finalHeight, 0);
mAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animator) {
// Height will be 0, but set visibility to 'GONE'
txtB.setVisibility(View.GONE);
}
#Override
public void onAnimationStart(Animator animator) {
}
#Override
public void onAnimationCancel(Animator animator) {
}
#Override
public void onAnimationRepeat(Animator animator) {
}
});
mAnimator.start();
}
public ValueAnimator slideAnimator(int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
// update height
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = txtB.getLayoutParams();
layoutParams.height = value;
txtB.setLayoutParams(layoutParams);
}
});
return animator;
}
}
}
GridView adapter (currently excluded from project)
private class MyGVAdapter extends ArrayAdapter<AdapterItem> {
private List<AdapterItem> items = new ArrayList<>();
MyGVAdapter(Context context, int textviewid) {
super(context, textviewid);
}
void addAdapterItem(MyGVFragment.AdapterItem item) {
items.add(item);
}
#Override
public int getCount() {
return items.size();
}
#Override
public MyGVFragment.AdapterItem getItem(int position) {
return ((null != items) ? items.get(position) : null);
}
#Override
public long getItemId(int position) {
return position;
}
#NonNull
#Override
public View getView(final int position, View convertView, #NonNull final ViewGroup parent) {
View rowView;
if (convertView == null) {
rowView = getActivity().getLayoutInflater().inflate(R.layout.gridview_item, parent, false);
} else {
rowView = convertView;
}
TextView tv = rowView.findViewById(R.id.item_gridview);
tv.setText(items.get(position).first);
return rowView;
}
}
class AdapterItem {
String first;
AdapterItem(String first) {
this.first = first;
}
}
}
gridview_item.xml
<?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" >
<TextView
android:id="#+id/item_gridview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="0dp"
android:paddingEnd="10dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="?android:attr/textColorPrimary"
/>
</LinearLayout>
CardView with GridView (recyclerview_item_gv)
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:id="#+id/cv_gv"
android:layout_marginBottom="20dp"
>
<LinearLayout
android:id="#+id/lineralayout_gv_main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp"
android:animateLayoutChanges="true">
<LinearLayout
android:id="#+id/linearlayout_gv_titlerow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="2dp"
android:weightSum="100">
<TextView
android:id="#+id/tv_gv_A"
android:layout_weight="90"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorPrimary"
style="#android:style/TextAppearance.Medium" />
<TextView
android:id="#+id/tv_gv_expandcollapse"
android:importantForAccessibility="no"
android:clickable="true"
android:focusable="true"
android:layout_weight="10"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:textColor="?android:attr/textColorPrimary"
style="#android:style/TextAppearance.Large" />
</LinearLayout>
<RelativeLayout
android:id="#+id/relativelayout_gv"
android:animateLayoutChanges="true"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<GridView
android:id="#+id/gv_a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:columnWidth="100dp"
android:numColumns="auto_fit"
android:layout_marginBottom="20dp"
android:stretchMode="columnWidth" />
</RelativeLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
CardView with TextView (recyclerview_item_tv.xml)
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:id="#+id/cv_tv"
android:layout_marginBottom="20dp">
<LinearLayout
android:id="#+id/linearlayout_gv_main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp"
android:animateLayoutChanges="true">
<LinearLayout
android:id="#+id/linearlayout_tv_titlerow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="2dp"
android:weightSum="100">
<TextView
android:id="#+id/tv_tv_A"
android:layout_weight="90"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorPrimary"
style="#android:style/TextAppearance.Medium" />
<TextView
android:id="#+id/tv_tv_expandcollapse"
android:importantForAccessibility="no"
android:clickable="true"
android:focusable="true"
android:layout_weight="10"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:textColor="?android:attr/textColorPrimary"
style="#android:style/TextAppearance.Large" />
</LinearLayout>
<RelativeLayout
android:id="#+id/relativelayout_tv"
android:animateLayoutChanges="true"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/tv_B"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorPrimary"
style="#android:style/TextAppearance.Large" />
</RelativeLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
Ok so I made a quick example of how to do it. This is how the Activity class I'm posting looks like, all items are in the same RecyclerView:
Bear in mind, it might not look like your code because I am trying to use (At least for me) best practices, and also shorten the amount of code and classes by containing a lot of things in the same class.
Here is the Activity:
public class RecyclerActivity extends AppCompatActivity {
RecyclerView recycler;
ArrayList<String> data;
RecyclerView.Adapter<ViewHolder> adapter;
private static final int ITEM_TYPE = 100;
private static final int HEADER_TYPE = 101;
private static final int HEADER_TYPE_2 = 102;
private static final int GRID_TYPE = 103;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycler);
// find recycler,
recycler = findViewById(R.id.recycler);
// set the layout
recycler.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
// init data,
data = new ArrayList<>();
data.add("Item A");
data.add("Item B");
data.add("Item C");
// create the adapter
adapter = createAdapter();
// set the adapter
recycler.setAdapter(adapter);
}
// creates the adapter,
private RecyclerView.Adapter<ViewHolder> createAdapter() {
return new RecyclerView.Adapter<ViewHolder>() {
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int type) {
switch (type) {
case HEADER_TYPE:
// inflate the layout,
ViewHolder holderHeader1 = new ViewHolder(inflateHelper(R.layout.header, parent));
// set an on click to the view here to create only one object,
holderHeader1.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// do something
}
});
return holderHeader1;
case HEADER_TYPE_2:
// inflate the layout,
ViewHolder holderHeader2 = new ViewHolder(inflateHelper(R.layout.header, parent));
// set an on click to the view here to create only one object,
holderHeader2.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// do something
}
});
return holderHeader2;
case ITEM_TYPE:
// inflate the layout,
ViewHolder holderItem = new ViewHolder(inflateHelper(R.layout.item, parent));
// set an on click to the view here to create only one object,
holderItem.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// do something
}
});
return holderItem;
case GRID_TYPE:
// inflate the layout,
ViewHolder holderGrid = new ViewHolder(inflateHelper(R.layout.grid, parent));
// set an on click to the view here to create only one object,
holderGrid.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// do something
}
});
return holderGrid;
default:
// inflate the layout,
ViewHolder holderItemDefault = new ViewHolder(inflateHelper(R.layout.item, parent));
// set an on click to the view here to create only one object,
holderItemDefault.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// do something
}
});
return holderItemDefault;
}
}
/**
* Keep the viewholder simple and the all the view finding here. This way you
* only have one viewholder.
*/
#Override
public void onBindViewHolder(#NonNull ViewHolder viewHolder, int position) {
// go through the positions
switch (getItemViewType(position)) {
case HEADER_TYPE:
Button expandButton = viewHolder.itemView.findViewById(R.id.button);
expandButton.setText("Expand");
break;
case HEADER_TYPE_2:
Button collapseButton = viewHolder.itemView.findViewById(R.id.button);
collapseButton.setText("Collapse");
break;
case ITEM_TYPE:
// get the current item
String item = data.get(position - 3);
TextView title = viewHolder.itemView.findViewById(R.id.title);
title.setText(item);
break;
case GRID_TYPE:
break;
}
}
#Override
public int getItemCount() {
return data.size() + 3;
}
#Override
public int getItemViewType(int position) {
switch (position) {
case 0:
return HEADER_TYPE;
case 1:
return HEADER_TYPE_2;
case 2:
return GRID_TYPE;
default: return ITEM_TYPE;
}
}
};
}
private View inflateHelper(int resId, ViewGroup parent) {
return LayoutInflater.from(this).inflate(resId, parent, false);
}
// inner class for viewholder to use,
class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(#NonNull View itemView) {
super(itemView);
}
}
}
You might notice that I have used one ViewHolder. This is a pattern I believe we should adopt because it simplifies code so much more. You can find the views you need in the onBind method and thus get away with using one for all types of views.
Also I add the on click listener in the onCreateViewHolder because it is much more efficient to set it there, and it allows for the viewholder to not be onClick specific.
As I said, you can use as many types as you want, you just need to check for it, and set it at the right position.
Here are the layout files if you are interested:
Header
<?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="wrap_content"
android:padding="4dp"
android:gravity="center">
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Expand" />
</LinearLayout>
Item
<?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="wrap_content"
android:padding="16dp"
android:elevation="2dp"
android:layout_margin="16dp"
android:background="#drawable/rounded">
<TextView
android:id="#+id/title"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="Item A"
android:textColor="#fff"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_chevron_right"
android:tint="#fff"/>
</LinearLayout>
Grid Layout (I cheated a bit here because I didn't see a reason to not hardcode vowels, although you might need to do it dynamically)
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:padding="16dp"
android:background="#drawable/rounded"
android:backgroundTint="#fff"
android:elevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingBottom="16dp"
android:text="French Vowels"
android:textStyle="bold"
style="#style/Base.TextAppearance.AppCompat.Medium"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<TextView
android:id="#+id/a"
android:layout_width="0dp"
android:layout_weight="1"
android:gravity="center"
android:layout_height="wrap_content"
android:text="a"
style="#style/Base.TextAppearance.AppCompat.Medium"/>
<TextView
android:id="#+id/e"
android:layout_width="0dp"
android:layout_weight="1"
android:gravity="center"
android:layout_height="wrap_content"
android:text="e"
style="#style/Base.TextAppearance.AppCompat.Medium"/>
<TextView
android:id="#+id/i"
android:layout_width="0dp"
android:layout_weight="1"
android:gravity="center"
android:layout_height="wrap_content"
android:text="i"
style="#style/Base.TextAppearance.AppCompat.Medium"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<TextView
android:id="#+id/o"
android:layout_width="0dp"
android:layout_weight="1"
android:gravity="center"
android:layout_height="wrap_content"
android:text="o"
style="#style/Base.TextAppearance.AppCompat.Medium"/>
<TextView
android:id="#+id/u"
android:layout_width="0dp"
android:layout_weight="1"
android:gravity="center"
android:layout_height="wrap_content"
android:text="u"
style="#style/Base.TextAppearance.AppCompat.Medium"/>
<TextView
android:id="#+id/y"
android:layout_width="0dp"
android:layout_weight="1"
android:gravity="center"
android:layout_height="wrap_content"
android:text="y"
style="#style/Base.TextAppearance.AppCompat.Medium"/>
</LinearLayout>
</LinearLayout>
</FrameLayout>
Rounded Drawable
<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#222"/>
<corners android:radius="6dp"/>
</shape>
For anyone reading, if you happen to use Kotlin, consider using my small library which eliminates this recycler view boilerplate to a simple chain of functions:
https://github.com/Pfuster12/BoilerCycle
I've implemented SmoothRefreshLayout library in my application, which has at the moment a simple ListView.
The problem occurs when I scroll down the ListView and, when I try to get to the top of it, instead of scrolling up, it invokes the Refresh listener. So the movement is stuck.
This is the activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<me.dkzwm.widget.srl.SmoothRefreshLayout
android:id="#+id/smoothRefreshLayout"
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ProgressBar
android:id="#+id/progressBar"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerInParent="true"
android:indeterminateDrawable="#drawable/circular_spinner" >
</ProgressBar>
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/lv"
/>
</RelativeLayout>
</me.dkzwm.widget.srl.SmoothRefreshLayout>
And this is the part of the RefreshListener in the onCreate:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().setTitle("");
setContentView(R.layout.activity_main);
refreshLayout = (SmoothRefreshLayout)findViewById(R.id.smoothRefreshLayout);
refreshLayout.setHeaderView(new MyCustomHeader(this));
refreshLayout.setEnabled(true);
refreshLayout.setOnRefreshListener(new RefreshingListenerAdapter() {
#Override
public void onRefreshBegin(boolean isRefresh) {
if (isNetworkAvailable(MainActivity2.this)) {
isRef = true;
new upd().execute();
} else {
Toast.makeText(MainActivity2.this, getResources().getString(R.string.err_conn), Toast.LENGTH_SHORT).show();
}
}
});
I know it has no sense to self reply, but it may help someone else.
Anyway, the solution has been simple, I have just implemented an onScrollListener and checked if first item was on the top, if not just disable the Refresher.
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView listView, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
int rowpos = (listView == null || listView.getChildCount() == 0) ?
0 : listView.getChildAt(0).getTop();
refreshLayout.setEnabled((rowpos >= 0));
}
});
Credits: Can't scroll in a ListView in a swipeRefreshLayout
Past few days I've been working on fixed footer for my recyclerview but so far I didn't get the result that I expected. I've already taken a look existing questions but got same result as before.It seems I 'm still missing a point but I can't figure out... Therefore I'm waiting for your suggestions. Thanks!
Here is the current view of layout, I want that FOOTER part to be sticky at bottom of recyclerview.
DrawerAdapter.java
public class DrawerAdapter extends RecyclerView.Adapter<DrawerAdapter.DrawerViewHolder> {
// Class for view types.
private class VIEW_TYPES {
public static final int Header = 0;
public static final int Normal = 1;
public static final int Footer = 2;
}
private ArrayList<DrawerItem> drawerMenuList;
private String userProfileName;
private String userProfileMail;
private Drawable userProfilePic;
private OnItemSelecteListener mListener;
public DrawerAdapter(ArrayList<DrawerItem> drawerMenuList, String name, String mail, Drawable profileImage) {
this.drawerMenuList = drawerMenuList;
this.userProfileMail = mail;
this.userProfileName = name;
this.userProfilePic = profileImage;
}
#Override
public DrawerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
if (viewType == VIEW_TYPES.Header) {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.header, parent, false);
} else if (viewType == VIEW_TYPES.Footer) {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.footer, parent, false);
} else {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_row, parent, false);
}
return new DrawerViewHolder(view, viewType);
}
#Override
public void onBindViewHolder(DrawerViewHolder holder, int position) {
// If item's position 0 it's a header.
if (position == 0) {
holder.headerName.setText(userProfileName);
holder.headerMail.setText(userProfileMail);
holder.headerProfile.setImageDrawable(userProfilePic);
} else if (position == 5) { // If position is 5 then it's footer.
holder.headerName.setText("FOOTER");
} else { // it's a menu item.
holder.title.setText(drawerMenuList.get(position - 1).getTitle());
holder.icon.setImageResource(drawerMenuList.get(position - 1).getIcon());
}
}
#Override
public int getItemCount() {
return drawerMenuList.size() + 1;
}
#Override
public int getItemViewType(int position) {
if (position == 0) {
return VIEW_TYPES.Header;
} else if (position == 5) {
return VIEW_TYPES.Footer;
} else {
return VIEW_TYPES.Normal;
}
}
class DrawerViewHolder extends RecyclerView.ViewHolder {
TextView title;
TextView headerName;
ImageView icon;
TextView headerMail;
ImageView headerProfile;
public DrawerViewHolder(View itemView, int viewType) {
super(itemView);
if (viewType == VIEW_TYPES.Header) {
headerName = (TextView) itemView.findViewById(R.id.header_name);
headerMail = (TextView) itemView.findViewById(R.id.header_email);
headerProfile = (ImageView) itemView.findViewById(R.id.circleView);
} else if (viewType == VIEW_TYPES.Footer) {
headerName = (TextView) itemView.findViewById(R.id.textView3);
} else {
title = (TextView) itemView.findViewById(R.id.title);
icon = (ImageView) itemView.findViewById(R.id.icon);
}
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mListener.onItemSelected(view, getAdapterPosition());
}
});
}
}
// To set click listener.
public interface OnItemSelecteListener {
void onItemSelected(View v, int position);
}
public void setOnItemClickLister(OnItemSelecteListener mListener) {
this.mListener = mListener;
}
}
MainActivity.java
for (int i = 0; i < navMenuTitles.length; i++) {
Log.e("I", String.valueOf(i));
mDrawerItemList.add(new DrawerItem(navMenuTitles[i], navMenuIcons.getResourceId(i, -1)));
}
mDrawerItemList.add(new DrawerItem());
mRecyclerView = (RecyclerView) findViewById(R.id.drawerRecyclerView);
mDrawerAdapter = new DrawerAdapter(mDrawerItemList, logged_user.name, logged_user.email, draw_userImage);
mLinearLayoutManager = new CustomGridLayoutManager(HomeScreenActivity.this){
#Override
public boolean canScrollVertically() {
return false;
}
};
mRecyclerView.setLayoutManager(mLinearLayoutManager);
mRecyclerView.setAdapter(mDrawerAdapter);
mDrawerLayout = (DrawerLayout) findViewById(R.id.DrawerLayout);
mDrawerToggle = new ActionBarDrawerToggle(HomeScreenActivity.this, mDrawerLayout, mToolbar, R.string.openDrawer, R.string.closeDrawer) {
#Override
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
// code here will execute once the drawer is opened( As I dont want anything happened whe drawer is
// open I am not going to put anything here)
}
#Override
public void onDrawerClosed(View drawerView) {
super.onDrawerClosed(drawerView);
// Code here will execute once drawer is closed
}
}; // Drawer Toggle Object Made
mDrawerLayout.addDrawerListener(mDrawerToggle); // Drawer Listener set to the Drawer toggle
mDrawerToggle.syncState(); // Finally we set the drawer toggle sync State
mDrawerAdapter.setOnItemClickLister(new DrawerAdapter.OnItemSelecteListener() {
#Override
public void onItemSelected(View v, int position) {
Log.e("CLICK", "You clicked at position: " + position);
}
});
drawer_layout.xml
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/DrawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:elevation="7dp">
<FrameLayout
android:id="#+id/containerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
<android.support.v7.widget.RecyclerView
android:id="#+id/drawerRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:layout_gravity="left"
android:background="#ffffff"
app:reverseLayout="false"
app:stackFromEnd="false">
</android.support.v7.widget.RecyclerView>
footer.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom"
android:layout_alignParentBottom="true"
android:background="#ffffff"
android:layout_alignBottom="#id/drawerRecyclerView"
android:foregroundGravity="bottom">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="false"
android:layout_alignParentEnd="true"
android:background="#f16666"
android:foregroundGravity="bottom"
android:gravity="center_horizontal|bottom"
android:paddingTop="#dimen/_15sdp"
android:paddingBottom="#dimen/_15sdp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="TEST"
android:id="#+id/textView3"
android:layout_gravity="center_horizontal"
android:layout_alignParentBottom="true"
android:gravity="center_horizontal|bottom" />
</LinearLayout>
</RelativeLayout>
In your drawer_layout.xml put RecyclerView inside a RelativeLayout and set alignParentTop to true. Then remove your footer view from your RecyclerView and put that inside the RelativeLayout below RecyclerView like this:
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/DrawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:elevation="7dp">
<FrameLayout
android:id="#+id/containerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/drawerRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_gravity="left"
android:background="#ffffff"
app:reverseLayout="false"
app:stackFromEnd="false" />
<include layout="#layout/footer" />
</RelativeLayout>
</android.support.v4.widget.DrawerLayout>
You could also take a look at NavigationView so you don't have to customize your drawer list. Here is a tutorial on how to use this. It is pretty easy to use and requires less code. You could then take a look at this answer on how to put a footer in NavigationView.
I am using LinearLayout with weight-ing (created multiple dimens files with different values). For me it works as excepted:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white"
android:orientation="vertical"
<include layout="#layout/header" />
<android.support.v7.widget.RecyclerView
android:id="#+id/recycleView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.5"
tools:layout_height="0dp"
tools:listitem="#layout/row" />
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="#dimen/footer_weight"
android:padding="#dimen/extra_padding"
android:paddingEnd="#dimen/small_padding"
android:paddingLeft="#dimen/small_padding"
android:paddingRight="#dimen/small_padding"
android:paddingStart="#dimen/small_padding"
android:text="#string/contact"
android:textColor="#color/grey" />
</LinearLayout>