Android Gridview with 2 column (But different imageview width size) - android

I want screen to look like following, with the help of gridview. I am able to add 1 column and two column using setSpanSizeLookup
But how to add two columns with dynamic width?
final GridLayoutManager gridLayoutManager=new GridLayoutManager(ExploreMenuActivity.this,2);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
if(position == 0)
{
return 2;
}else {
return 1;
}
}
});
Update :
I have used Flexbox layout by google .
implementation 'com.google.android:flexbox:0.3.1'
My Main activity
FlexboxLayoutManager flexboxLayoutManager=new FlexboxLayoutManager(ExploreMenuActivity.this);
flexboxLayoutManager.setFlexWrap(FlexWrap.WRAP);
flexboxLayoutManager.setAlignItems(AlignItems.STRETCH);
recycler_ExploreProduct.setLayoutManager(flexboxLayoutManager);
My .xml file
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/img"
android:layout_width="#dimen/_140sdp"
android:background="#color/color_white_gray"
android:layout_height="#dimen/_140sdp"
android:scaleType="fitCenter"
android:src="#drawable/p_7" />
and My adapter
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
Drawable drawable = ResourcesCompat.getDrawable(context.getResources(), aryImages[position], null);
holder.bindTo(drawable, position);
}
class ViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
public ViewHolder(View itemView) {
super(itemView);
imageView = (ImageView) itemView.findViewById(R.id.img);
}
void bindTo(Drawable drawable, int position) {
imageView.setImageDrawable(drawable);
ViewGroup.LayoutParams lp = imageView.getLayoutParams();
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(displayMetrics);
int screenWidth = displayMetrics.widthPixels;
Log.d(TAG,"## bindTo before if");
if (lp instanceof FlexboxLayoutManager.LayoutParams) {
Log.d(TAG,"## bindTo inside if");
FlexboxLayoutManager.LayoutParams flexboxLp = (FlexboxLayoutManager.LayoutParams) lp;
if (position == 0) {
flexboxLp.width = screenWidth / 3;
} else if (position == 1) {
flexboxLp.width = screenWidth / 2;
} else if (position == 2) {
flexboxLp.width = screenWidth / 2;
} else if (position == 3) {
flexboxLp.width = screenWidth / 3;
} else if (position == 4) {
flexboxLp.width = screenWidth / 3;
} else if (position == 5) {
flexboxLp.width = screenWidth / 2;
} else if (position == 6) {
flexboxLp.width = screenWidth;
} else if (position == 7) {
flexboxLp.width = screenWidth / 3;
} else if (position == 8) {
flexboxLp.width = screenWidth / 2;
} else if (position == 9) {
flexboxLp.width = screenWidth;
} else if (position == 10) {
flexboxLp.width = screenWidth / 2;
} else if (position == 11) {
flexboxLp.width = screenWidth / 3;
} else if (position == 12) {
flexboxLp.width = screenWidth;
} else if (position == 13) {
flexboxLp.width = screenWidth / 3;
} else if (position == 14) {
flexboxLp.width = screenWidth / 2;
} else if (position == 15) {
flexboxLp.width = screenWidth / 2;
} else if (position == 16) {
flexboxLp.width = screenWidth / 3;
} else if (position == 17) {
flexboxLp.width = screenWidth;
} else if (position == 18) {
flexboxLp.width = screenWidth / 3;
} else if (position == 19) {
flexboxLp.width = screenWidth / 2;
}
}
}
Right now I am able to achieve output using above static conditions,
How can I do this things dynamic, when my data will come from API.

You need to use stagard LayoutManager in Recyclerview to get the required results.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/rl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".MainActivity"
android:background="#ffffff"
>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
>
</android.support.v7.widget.RecyclerView>
</RelativeLayout>
MainActivity.java
package com.cfsuman.me.androidcode;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.Window;
import android.widget.RelativeLayout;
public class MainActivity extends AppCompatActivity {
private Context mContext;
RelativeLayout mRelativeLayout;
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Request window feature action bar
requestWindowFeature(Window.FEATURE_ACTION_BAR);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Get the application context
mContext = getApplicationContext();
// Change the action bar color
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(Color.RED));
// Get the widgets reference from XML layout
mRelativeLayout = (RelativeLayout) findViewById(R.id.rl);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
// Initialize a new String array
String[] colors = {
"Red","Green","Blue","Yellow","Magenta","Cyan","Orange",
"Aqua","Azure","Beige","Bisque","Brown","Coral","Crimson"
};
/*
StaggeredGridLayoutManager
A LayoutManager that lays out children in a staggered grid formation. It supports
horizontal & vertical layout as well as an ability to layout children in reverse.
Staggered grids are likely to have gaps at the edges of the layout. To avoid these
gaps, StaggeredGridLayoutManager can offset spans independently or move items
between spans. You can control this behavior via setGapStrategy(int).
*/
/*
public StaggeredGridLayoutManager (int spanCount, int orientation)
Creates a StaggeredGridLayoutManager with given parameters.
Parameters
spanCount : If orientation is vertical, spanCount is number of columns.
If orientation is horizontal, spanCount is number of rows.
orientation : VERTICAL or HORIZONTAL
*/
// Define a layout for RecyclerView
mLayoutManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(mLayoutManager);
// Initialize a new instance of RecyclerView Adapter instance
mAdapter = new ColorAdapter(mContext,colors);
// Set the adapter for RecyclerView
mRecyclerView.setAdapter(mAdapter);
}
}
ColorAdapter.java
package com.cfsuman.me.androidcode;
import android.content.Context;
import android.graphics.Color;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.Random;
public class ColorAdapter extends RecyclerView.Adapter<ColorAdapter.ViewHolder>{
private String[] mDataSet;
private Context mContext;
private Random mRandom = new Random();
public ColorAdapter(Context context,String[] DataSet){
mDataSet = DataSet;
mContext = context;
}
public static class ViewHolder extends RecyclerView.ViewHolder{
public TextView mTextView;
public ViewHolder(View v){
super(v);
mTextView = (TextView)v.findViewById(R.id.tv);
}
}
#Override
public ColorAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
// Create a new View
View v = LayoutInflater.from(mContext).inflate(R.layout.custom_view,parent,false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position){
holder.mTextView.setText(mDataSet[position]);
// Set a random height for TextView
holder.mTextView.getLayoutParams().height = getRandomIntInRange(250,75);
// Set a random color for TextView background
holder.mTextView.setBackgroundColor(getRandomHSVColor());
}
#Override
public int getItemCount(){
return mDataSet.length;
}
// Custom method to get a random number between a range
protected int getRandomIntInRange(int max, int min){
return mRandom.nextInt((max-min)+min)+min;
}
// Custom method to generate random HSV color
protected int getRandomHSVColor(){
// Generate a random hue value between 0 to 360
int hue = mRandom.nextInt(361);
// We make the color depth full
float saturation = 1.0f;
// We make a full bright color
float value = 1.0f;
// We avoid color transparency
int alpha = 255;
// Finally, generate the color
int color = Color.HSVToColor(alpha, new float[]{hue, saturation, value});
// Return the color
return color;
}
}
build.gradle [dependencies]
compile 'com.android.support:recyclerview-v7:23.0.1'
compile 'com.android.support:cardview-v7:23.0.1'

use this https://github.com/felipecsl/AsymmetricGridView
http://myhexaville.com/2017/02/27/android-flexboxlayout/
I am sure this will help you.

You need to use flexbox layout
`<com.google.android.flexbox.FlexboxLayout
android:id="#+id/flex_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:justifyContent="flex_start"
app:alignContent="flex_start"
app:alignItems="flex_start"
app:flexDirection="row"
app:flexWrap="wrap">
</com.google.android.flexbox.FlexboxLayout>`
and then add view dynamically like
`TextView rowTextView = (TextView)view.findViewById(R.id.flex_tv);
rowTextView.setTag(items.get(i));
rowTextView.setId(i);
rowTextView.setText(items.get(i));
flexboxLayout.addView(view);`
Please add compile 'com.google.android:flexbox:0.3.0#aar' in your gradle

Use LinearLayout with equal weight for both ImageViews Something like below xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/image1"
android:layout_width="0dp"
android:src="#mipmap/ic_launcher"
android:layout_weight="1"
android:background="#ff0000"
android:layout_height="wrap_content"/>
<ImageView
android:id="#+id/image2"
android:layout_width="0dp"
android:layout_weight="1"
android:background="#ffff00"
android:src="#mipmap/ic_launcher"
android:layout_height="wrap_content"/>
</LinearLayout>

Related

View Pager with previous and next item smaller in size with infinite scroll

Want to create the view pager same as following UI, applied custom transformer but not working.
ViewPager.java
public class MyViewPager extends ViewPager implements ViewPager.PageTransformer {
public static final String TAG = "MyViewPager";
private float MAX_SCALE = 0.0f;
private int mPageMargin;
private boolean animationEnabled=true;
private boolean fadeEnabled=false;
private float fadeFactor=0.5f;
public MyViewPager(Context context) {
this(context, null);
}
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
// clipping should be off on the pager for its children so that they can scale out of bounds.
setClipChildren(false);
setClipToPadding(false);
// to avoid fade effect at the end of the page
setOverScrollMode(2);
setPageTransformer(false, this);
setOffscreenPageLimit(3);
mPageMargin = dp2px(context.getResources(), 50);
setPadding(mPageMargin, mPageMargin, mPageMargin, mPageMargin);
}
public int dp2px(Resources resource, int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resource.getDisplayMetrics());
}
public void setAnimationEnabled(boolean enable) {
this.animationEnabled = enable;
}
public void setFadeEnabled(boolean fadeEnabled) {
this.fadeEnabled = fadeEnabled;
}
public void setFadeFactor(float fadeFactor) {
this.fadeFactor = fadeFactor;
}
#Override
public void setPageMargin(int marginPixels) {
mPageMargin = marginPixels;
// setPadding(mPageMargin, mPageMargin, mPageMargin, mPageMargin);
}
#Override
public void transformPage(View page, float position) {
if (mPageMargin <= 0|| !animationEnabled)
return;
page.setPadding(mPageMargin / 3, mPageMargin / 3, mPageMargin / 3, mPageMargin / 3);
if (MAX_SCALE == 0.0f && position > 0.0f && position < 1.0f) {
MAX_SCALE = position;
}
position = position - MAX_SCALE;
float absolutePosition = Math.abs(position);
if (position <= -1.0f || position >= 1.0f) {
if(fadeEnabled)
page.setAlpha(fadeFactor);
// Page is not visible -- stop any running animations
} else if (position == 0.0f) {
// Page is selected -- reset any views if necessary
page.setScaleX((1 + MAX_SCALE));
page.setScaleY((1 + MAX_SCALE));
page.setAlpha(1);
} else {
page.setScaleX(1 + MAX_SCALE * (1 - absolutePosition));
page.setScaleY(1 + MAX_SCALE * (1 - absolutePosition));
if(fadeEnabled)
page.setAlpha( Math.max(fadeFactor, 1 - absolutePosition));
}
}
}
UPDATE - if you want to make current page zoom use below PageTransformer
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;
public class JavaActivity extends AppCompatActivity {
ViewPager2 myViewPager2;
MyAdapter MyAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_java);
myViewPager2 = findViewById(R.id.viewpager);
MyAdapter = new MyAdapter(this);
myViewPager2.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
myViewPager2.setAdapter(MyAdapter);
myViewPager2.setOffscreenPageLimit(3);
float pageMargin = getResources().getDimensionPixelOffset(R.dimen.pageMargin);
float pageOffset = getResources().getDimensionPixelOffset(R.dimen.offset);
myViewPager2.setPageTransformer((page, position) -> {
float myOffset = position * -(2 * pageOffset + pageMargin);
if (position < -1) {
page.setTranslationX(-myOffset);
} else if (position <= 1) {
float scaleFactor = Math.max(0.7f, 1 - Math.abs(position - 0.14285715f));
page.setTranslationX(myOffset);
page.setScaleY(scaleFactor);
page.setAlpha(scaleFactor);
} else {
page.setAlpha(0);
page.setTranslationX(myOffset);
}
});
}
}
OUTPUT
NOTE: you can download complete code from my GitHub repositories
Try this way
JavaActivity
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.ViewCompat;
import androidx.viewpager2.widget.ViewPager2;
public class JavaActivity extends AppCompatActivity {
ViewPager2 myViewPager2;
MyAdapter MyAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_java);
myViewPager2 = findViewById(R.id.viewpager);
MyAdapter = new MyAdapter(this);
myViewPager2.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
myViewPager2.setAdapter(MyAdapter);
myViewPager2.setOffscreenPageLimit(3);
float pageMargin= getResources().getDimensionPixelOffset(R.dimen.pageMargin);
float pageOffset = getResources().getDimensionPixelOffset(R.dimen.offset);
myViewPager2.setPageTransformer(new ViewPager2.PageTransformer() {
#Override
public void transformPage(#NonNull View page, float position) {
float myOffset = position * -(2 * pageOffset + pageMargin);
if (myViewPager2.getOrientation() == ViewPager2.ORIENTATION_HORIZONTAL) {
if (ViewCompat.getLayoutDirection(myViewPager2) == ViewCompat.LAYOUT_DIRECTION_RTL) {
page.setTranslationX(-myOffset);
} else {
page.setTranslationX(myOffset);
}
} else {
page.setTranslationY(myOffset);
}
}
});
}
}
activity_java layout 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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".JavaActivity">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/viewpager"
android:clipToPadding="false"
android:clipChildren="false"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
>
MyAdapter
import android.content.Context;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private Context context;
public MyAdapter(Context context) {
this.context = context;
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.row_item, parent, false);
return new MyViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull MyViewHolder holder, int position) {
holder.tvName.setText(String.format("Row number%d", position));
if (position % 2 ==0){
holder.imgBanner.setBackgroundColor(Color.RED);
}else {
holder.imgBanner.setBackgroundColor(Color.GREEN);
}
}
#Override
public int getItemCount() {
return 15;
}
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView tvName;
ImageView imgBanner;
public MyViewHolder(#NonNull View itemView) {
super(itemView);
tvName = itemView.findViewById(R.id.tvName);
imgBanner = itemView.findViewById(R.id.imgBanner);
}
}
}
row_item layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_marginLeft="#dimen/pageMarginAndOffset"
android:layout_marginRight="#dimen/pageMarginAndOffset"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="#+id/imgBanner"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorAccent"
android:contentDescription="#string/app_name"/>
<TextView
android:id="#+id/tvName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:layout_centerInParent="true"
android:textColor="#android:color/white"
android:textSize="20sp"
android:textStyle="bold"
tools:text="Hello"/>
</RelativeLayout>
OUTPUT
Using ViewPager2 (that has RecyclerView in it).
vp2 is instance of ViewPager2
This worked for me:
RecyclerView rv = (RecyclerView) vp2.getChildAt(0);
rv.setPadding(80, 0, 80, 0);
rv.setClipToPadding(false);
I used FragmentStateAdapter for it and overriding getItemId() and containsItem() for add/remove fragments.
result is here:
You can make use of clipToPadding and programmatically adding the pageMargin and padding to your viewpager.
Try something like this-
viewpager.setClipToPadding(false);
viewpager.setPadding(40, 0, 70, 0);
viewpager.setPageMargin(20);

Get the center visible item value of Infinite RecyclerView

i have created a circular recyclerview by making adapter count into Integer.MAX.Now, i need to highlight the center recycler item like in the image.Kindly help me!!
By using center indicator(textview) in the layout and addOnScrollListner we can achieve this
please refer the following example
In xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="wrap_content"
android:text="↓"
android:id="#+id/centerIndicator"
android:textSize="24sp"
android:textStyle="bold"
android:visibility="visible"
android:textColor="#color/theme_yellow"
android:layout_centerHorizontal="true"
android:layout_height="wrap_content"
android:layout_marginTop="27dp"
android:background="#android:color/transparent"
/>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:id="#+id/list"
android:clipToPadding="false"
android:divider="#android:color/transparent"
android:layout_height="wrap_content"/>
</RelativeLayout>
In Activity/Fragment:
public class Sample extends Fragment {
RecyclerView listView;
ArrayList<String>mWeekDaysList=new ArrayList<>();
LinearLayoutManager mlinearLayoutManagerForDateList;
DateAdapter mDateAdapter;
TimeListAdapter mtimeAdapter;
private int mCenterPivot;
private boolean mAutoSet = true;
Activity mactivity;
public NigaichiNiralFrag() {
// Required empty public constructor
}
#Override
public View onCreateView(final LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view= inflater.inflate(R.layout.fragment_nigaichi_niral, container, false);
mactivity=getActivity();
mWeekDaysList.add("Sunday");
mWeekDaysList.add("Monday");
mWeekDaysList.add("Tuesday");
mWeekDaysList.add("Wednesday");
mWeekDaysList.add("Thursday");
mWeekDaysList.add("Friday");
mWeekDaysList.add("Saturday");
listView = (RecyclerView) view.findViewById(R.id.list);
mlinearLayoutManagerForDateList = new LinearLayoutManager(mactivity);
mlinearLayoutManagerForDateList.setOrientation(LinearLayoutManager.HORIZONTAL);
listView.setLayoutManager(mlinearLayoutManagerForDateList);
final TextView mCenterIndicator = (TextView) view.findViewById(R.id.centerIndicator);
final int itemWidth = (int) getResources().getDimension(R.dimen.flexible_space_image_height) ;
mlinearLayoutManagerForDateList.scrollToPosition(Integer.MAX_VALUE / 2);
mDateAdapter=new DateAdapter(mWeekDaysList);
listView.setAdapter(mDateAdapter);
mCenterIndicator.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int center = ( mCenterIndicator.getLeft() + mCenterIndicator.getRight() ) / 2 ;
int padding = center - itemWidth / 2; //Assuming both left and right padding needed are the same
listView.setPadding(5,0,5,0);
mCenterPivot = center;
listView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
LinearLayoutManager lm = (LinearLayoutManager) recyclerView.getLayoutManager();
if( mCenterPivot == 0 ) {
// Default pivot , Its a bit inaccurate .
// Better pass the center pivot as your Center Indicator view's
// calculated center on it OnGlobalLayoutListener event
mCenterPivot = lm.getOrientation() == LinearLayoutManager.HORIZONTAL ? ( recyclerView.getLeft() + recyclerView.getRight() ) : ( recyclerView.getTop() + recyclerView.getBottom() );
}
if( !mAutoSet ) {
if( newState == RecyclerView.SCROLL_STATE_IDLE ) {
//ScrollStoppped
View view = findCenterView(lm);//get the view nearest to center
//view.setBackgroundColor(Color.RED);
int position = recyclerView.getChildAdapterPosition(view) % mWeekDaysList.size();
Log.d("isideScroll",mWeekDaysList.get(position));
mDateAdapter.setSelecteditem(position);
int viewCenter = lm.getOrientation() == LinearLayoutManager.HORIZONTAL ? ( view.getLeft() + view.getRight() )/2 :( view.getTop() + view.getBottom() )/2;
//compute scroll from center
int scrollNeeded = viewCenter - mCenterPivot; // Add or subtract any offsets you need here
if( lm.getOrientation() == LinearLayoutManager.HORIZONTAL ) {
recyclerView.smoothScrollBy(scrollNeeded, 0);
}
else
{
recyclerView.smoothScrollBy(0, (int) (scrollNeeded));
}
mAutoSet =true;
}
}
if( newState == RecyclerView.SCROLL_STATE_DRAGGING || newState == RecyclerView.SCROLL_STATE_SETTLING ){
mAutoSet =false;
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
}
});
return returnView;
}
private void scrollToCenter(View v) {
int itemToScroll = listView.getChildAdapterPosition(v);
int centerOfScreen = listView.getWidth() / 2 - v.getWidth() / 2;
//v.setBackgroundColor(Color.RED);
mlinearLayoutManagerForDateList.scrollToPositionWithOffset(itemToScroll, centerOfScreen);
}
private View findCenterView(LinearLayoutManager lm) {
int minDistance = 0;
View view = null;
View returnView = null;
boolean notFound = true;
for(int i = lm.findFirstVisibleItemPosition(); i <= lm.findLastVisibleItemPosition() && notFound ; i++ ) {
view=lm.findViewByPosition(i);
int center = lm.getOrientation() == LinearLayoutManager.HORIZONTAL ? ( view.getLeft() + view.getRight() )/ 2 : ( view.getTop() + view.getBottom() )/ 2;
int leastDifference = Math.abs(mCenterPivot - center);
if( leastDifference <= minDistance || i == lm.findFirstVisibleItemPosition())
{
minDistance = leastDifference;
returnView=view;
}
else
{
notFound=false;
}
}
return returnView;
}
}
Adapter:
public class DateAdapter extends RecyclerView.Adapter<DateAdapter.ReviewHolder> {
ArrayList<String> mData;
private int selectedItem = -1;
int pos=0;
public DateAdapter(ArrayList<String> data){
mData=data;
}
#Override
public ReviewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
View v= LayoutInflater.from(context).inflate(R.layout.item_horz,parent,false);
return new DateAdapter.ReviewHolder(v);
}
#Override
public void onBindViewHolder(ReviewHolder holder, int position) {
pos=position;
position = position % mData.size();
holder.tvName.setText(mData.get(position));
holder.tvName.setGravity(Gravity.CENTER);
if (position == selectedItem) {
Log.d("CenterPosition", "center" + position);
holder.tvName.setTextColor(Color.RED);
holder.tvName.setTextSize(20);
holder.tvName.setBackgroundColor(Color.parseColor("#fccd00"));
} else {
holder.tvName.setTextColor(Color.WHITE);
holder.tvName.setTextSize(16);
holder.tvName.setBackgroundColor(Color.BLACK);
}
}
#Override
public int getItemCount() {
// return mData.size();
return Integer.MAX_VALUE;
}
public class ReviewHolder extends RecyclerView.ViewHolder {
protected TextView tvName;
View container;
public ReviewHolder(View itemView) {
super(itemView);
container=itemView;
tvName= (TextView) itemView.findViewById(R.id.text);
}
}
public void setSelecteditem(int selecteditem) {
Log.d("POSITION",String.valueOf(selecteditem));
this.selectedItem = selecteditem;
notifyDataSetChanged();
}
}
item_horz.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="151dp"
android:id="#+id/wrapper"
android:background="#color/white"
android:orientation="horizontal"
android:layout_height="50dp">
<LinearLayout
android:layout_width="150dp"
android:layout_height="50dp"
android:background="#color/black">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="20sp"
android:id="#+id/text"
android:textColor="#color/white"
android:text="21"
android:gravity="center"
/>
</LinearLayout>
</LinearLayout>
Hope this will help you guys..
if you want to use horizontalscrollview (create items dynamically)instead recyclerview.
create your parent layout
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true">
<LinearLayout
android:layout_alignParentBottom="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"/>
</HorizontalScrollView>
inflate your childs items to parent layout.
LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
for (int i = 0; i < list.size(); i++) {
// inflate child element
final LinearLayout titleRow = (LinearLayout) inflater.inflate(R.layout.item_gallery, fragmentGalleryScrollLl, false);
final CircleImageView shapeImageView = (CircleImageView) titleRow.findViewById(R.id.item_gallery_iv);
shapeImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
scrollItem(titleRow);
}
});
fragmentGalleryScrollLl.addView(titleRow);
}
use this method when your click the items.
private void scrollItem(LinearLayout titleRow) {
int scrollX = (titleRow.getLeft() - (fragmentGalleryScrollSv.getWidth() / 2)) + (titleRow.getWidth() / 2);
fragmentGalleryScrollSv.smoothScrollTo(scrollX, 0);
}
Inside BindView, you can check middle position of the adapter and apply logic for image for that specific holder.
onBindViewHolder(View holder, int postion){
if(position == getItemCount() / 2)
{ //Write image logic for holder
}}
Callback to get center item position when scrolling stopped
import android.content.Context
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class RecyclerCenterItemFinder(
private val context: Context,
private val layoutManager: LinearLayoutManager,
private val callback: (Int) -> Unit,
private val controlState: Int = RecyclerView.SCROLL_STATE_IDLE
) :
RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (controlState == ALL_STATES || newState == controlState) {
val firstVisible = layoutManager.findFirstVisibleItemPosition()
val lastVisible = layoutManager.findLastVisibleItemPosition()
val itemsCount = lastVisible - firstVisible + 1
val screenCenter: Int = context.resources.displayMetrics.widthPixels / 2
var minCenterOffset = Int.MAX_VALUE
var middleItemIndex = 0
for (index in 0 until itemsCount) {
val listItem = layoutManager.getChildAt(index) ?: return
val topOffset = listItem.top
val bottomOffset = listItem.bottom
val centerOffset =
Math.abs(topOffset - screenCenter) + Math.abs(bottomOffset - screenCenter)
if (minCenterOffset > centerOffset) {
minCenterOffset = centerOffset
middleItemIndex = index + firstVisible
}
}
callback(middleItemIndex)
}
}
companion object {
const val ALL_STATES = 10
}
}
recycler.addOnScrollListener(RecyclerCenterItemFinder(requireContext(),
recycler.layoutManager,
{ centerItemPosition ->
// do something
}
))

GridLayoutManager - how to auto fit columns?

I have a RecyclerView with a GridLayoutManager that displays Card Views. I want the cards to rearrange according to the screen size (the Google Play app does this kind of thing with its app cards). Here is an example:
Here is how my app looks at the moment:
As you can see the cards just stretch and don't fit the empty space that is made from the orientation change. So how can I do this?
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Json;
using System.Threading;
using System.Threading.Tasks;
using Android.Media;
using Android.App;
using Android.Support.V4.App;
using Android.Support.V4.Content.Res;
using Android.Support.V4.Widget;
using Android.Support.V7.Widget;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Android.Widget;
using Android.Net;
using Android.Views.Animations;
using Android.Graphics;
using Android.Graphics.Drawables;
using Newtonsoft.Json;
using *******.Adapters;
using *******.Models;
namespace *******.Fragments {
public class Dashboard : GridLayoutBase {
private ISharedPreferences pref;
private SessionManager session;
private string cookie;
private DeviceModel deviceModel;
private RecyclerView recyclerView;
private RecyclerView.Adapter adapter;
// private RecyclerView.LayoutManager layoutManager;
private GridLayoutManager gridLayoutManager;
private List<ItemData> itemData;
private Bitmap lastPhotoBitmap;
private Drawable lastPhotoDrawable;
private static Activity activity;
private ProgressDialog progressDialog;
private TextView noData;
private const string URL_DASHBOARD = "http://192.168.1.101/appapi/getdashboard";
private const string URL_DATA = "http://192.168.1.101/appapi/getdata";
public override void OnCreate(Bundle bundle) {
base.OnCreate(bundle);
activity = Activity;
session = new SessionManager();
pref = activity.GetSharedPreferences("UserSession", FileCreationMode.Private);
cookie = pref.GetString("PHPSESSID", string.Empty);
}
public async override void OnStart() {
base.OnStart();
progressDialog = ProgressDialog.Show(activity, String.Empty, GetString(Resource.String.loading_text));
progressDialog.Window.ClearFlags(WindowManagerFlags.DimBehind);
await GetDevicesInfo();
if (deviceModel.Error == "true" && deviceModel.ErrorType == "noSensors") {
recyclerView.Visibility = ViewStates.Gone;
noData.Visibility = ViewStates.Visible;
progressDialog.Hide();
return;
} else {
recyclerView.Visibility = ViewStates.Visible;
noData.Visibility = ViewStates.Gone;
await PopulateSensorStates();
}
// DisplayLastPhoto();
adapter = new ViewAdapter(itemData);
new System.Threading.Thread(new System.Threading.ThreadStart(() => {
activity.RunOnUiThread(() => {
recyclerView.SetAdapter(adapter);
});
})).Start();
progressDialog.Hide();
}
public async Task GetDevicesInfo() {
var jsonFetcher = new JsonFetcher();
JsonValue jsonDashboard = await jsonFetcher.FetchDataWithCookieAsync(URL_DASHBOARD, cookie);
deviceModel = new DeviceModel();
deviceModel = JsonConvert.DeserializeObject<DeviceModel>(jsonDashboard);
}
// Shows sensor states
public async Task PopulateSensorStates() {
itemData = new List<ItemData>();
string lastValue = String.Empty;
foreach (var sensor in this.deviceModel.Sensors) {
var sensorImage = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.smoke_red, null);
switch (sensor.Type) {
case "2":
var jsonFetcher = new JsonFetcher();
JsonValue jsonData = await jsonFetcher.FetchSensorDataAsync(URL_DATA, sensor.Id, "DESC", "1", cookie);
var deviceModel = new DeviceModel();
deviceModel = JsonConvert.DeserializeObject<DeviceModel>(jsonData);
lastValue = deviceModel.SensorData.Last().Value;
break;
case "4":
await RenderLastCameraPhoto();
sensorImage = new BitmapDrawable(Resources, lastPhotoBitmap);
break;
}
itemData.Add(new ItemData() {
id = sensor.Id,
value = lastValue,
type = sensor.Type,
image = sensorImage,
title = sensor.Name.First().ToString().ToUpper() + sensor.Name.Substring(1).ToLower(),
});
}
}
// Shows the last camera photo
public async Task RenderLastCameraPhoto() {
if (deviceModel.Error == "true" && deviceModel.ErrorType == "noPhoto") {
//TODO: Show a "No photo" picture
} else {
string url = deviceModel.LastPhotoLink;
lastPhotoBitmap = await new ImageDownloader().GetImageBitmapFromUrlAsync(url, activity, 300, 300);
}
}
public async void UpdateData(bool isSwipeRefresh) {
await GetDevicesInfo();
if (deviceModel.Error == "true" && deviceModel.ErrorType == "noSensors") {
recyclerView.Visibility = ViewStates.Gone;
noData.Visibility = ViewStates.Visible;
return;
} else {
recyclerView.Visibility = ViewStates.Visible;
noData.Visibility = ViewStates.Gone;
await PopulateSensorStates();
}
adapter = new ViewAdapter(itemData);
new System.Threading.Thread(new System.Threading.ThreadStart(() => {
activity.RunOnUiThread(() => {
recyclerView.SetAdapter(adapter);
});
})).Start();
adapter.NotifyDataSetChanged();
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.Inflate(Resource.Layout.Dashboard, container, false);
noData = view.FindViewById<TextView>(Resource.Id.no_data_title);
SwipeRefreshLayout swipeRefreshLayout = view.FindViewById<SwipeRefreshLayout>(Resource.Id.swipe_container);
// swipeRefreshLayout.SetColorSchemeResources(Color.LightBlue, Color.LightGreen, Color.Orange, Color.Red);
// On refresh button press/swipe, updates the recycler view with new data
swipeRefreshLayout.Refresh += (sender, e) => {
UpdateData(true);
swipeRefreshLayout.Refreshing = false;
};
var gridLayoutManager = new GridLayoutManager(activity, 2);
recyclerView = view.FindViewById<RecyclerView>(Resource.Id.dashboard_recycler_view);
recyclerView.HasFixedSize = true;
recyclerView.SetLayoutManager(gridLayoutManager);
recyclerView.SetItemAnimator(new DefaultItemAnimator());
recyclerView.AddItemDecoration(new SpaceItemDecoration(15));
return view;
}
public class ViewAdapter : RecyclerView.Adapter {
private List<ItemData> itemData;
public string sensorId;
public string sensorType;
private ImageView imageId;
private TextView sensorValue;
private TextView sensorTitle;
public ViewAdapter(List<ItemData> itemData) {
this.itemData = itemData;
}
public class ItemView : RecyclerView.ViewHolder {
public View mainView { get; set; }
public string id { get; set; }
public string type { get; set; }
public ImageView image { get; set; }
// public TextView value { get; set; }
public TextView title { get; set; }
public ItemView(View view) : base(view) {
mainView = view;
}
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) {
View itemLayoutView = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.DashboardItems, null);
CardView cardView = itemLayoutView.FindViewById<CardView>(Resource.Id.dashboard_card_view);
imageId = itemLayoutView.FindViewById<ImageView>(Resource.Id.sensor_image);
// sensorValue = itemLayoutView.FindViewById<TextView>(Resource.Id.sensor_value);
sensorTitle = itemLayoutView.FindViewById<TextView>(Resource.Id.sensor_title);
var viewHolder = new ItemView(itemLayoutView) {
id = sensorId,
type = sensorType,
image = imageId,
// value = sensorValue,
title = sensorTitle
};
return viewHolder;
}
public override void OnBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
ItemView itemHolder = viewHolder as ItemView;
itemHolder.image.SetImageDrawable(itemData[position].image);
if (itemData[position].type == "2") { // Temperature
itemHolder.title.Text = itemData[position].title + ": " + itemData[position].value;
} else {
itemHolder.title.Text = itemData[position].title;
}
var bundle = new Bundle();
var dualColumnList = new DualColumnList();
var gallery = new Gallery();
EventHandler clickUpdateViewEvent = ((sender, e) => {
bundle.PutString("id", itemData[position].id);
gallery.Arguments = bundle;
dualColumnList.Arguments = bundle;
if (itemData[position].type == "4") { // Camera
((FragmentActivity)activity).ShowFragment(gallery, itemData[position].title, itemData[position].type, true);
} else {
((FragmentActivity)activity).ShowFragment(dualColumnList, itemData[position].title, itemData[position].type, true);
}
});
itemHolder.image.Click += clickUpdateViewEvent;
// itemHolder.value.Click += clickUpdateViewEvent;
itemHolder.title.Click += clickUpdateViewEvent;
}
public override int ItemCount {
get { return itemData.Count; }
}
}
public class ItemData {
public string id { get; set; }
public string type { get; set; }
public Drawable image { get; set; }
public string value { get; set; }
public string title { get; set; }
}
}
}
Fragment Layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/swipe_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:weightSum="1">
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.9"
android:scrollbars="vertical">
<android.support.v7.widget.RecyclerView
android:id="#+id/dashboard_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:text="#string/no_data_text"
android:id="#+id/no_data_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="30sp"
android:gravity="center"
android:layout_centerInParent="true" />
</RelativeLayout>
</LinearLayout>
</android.support.v4.widget.SwipeRefreshLayout>
Fragment Items Layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/dashboard_card_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:foreground="?android:attr/selectableItemBackground">
<ImageView
android:id="#+id/sensor_image"
android:layout_width="120dp"
android:layout_height="120dp"
android:paddingTop="5dp"
android:layout_alignParentTop="true" />
<!-- <TextView
android:id="#+id/sensor_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:layout_below="#id/sensor_image"
android:gravity="center" />-->
<TextView
android:id="#+id/sensor_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="23sp"
android:layout_below="#id/sensor_image"
android:gravity="center"
android:layout_alignParentBottom="true" />
</LinearLayout>
</android.support.v7.widget.CardView>
You can calculate available number of columns, given a desired column width, and load the image as calculated. Define a static funtion to calculate as:
public class Utility {
public static int calculateNoOfColumns(Context context, float columnWidthDp) { // For example columnWidthdp=180
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
float screenWidthDp = displayMetrics.widthPixels / displayMetrics.density;
int noOfColumns = (int) (screenWidthDp / columnWidthDp + 0.5); // +0.5 for correct rounding to int.
return noOfColumns;
}
}
And then when using it in the activity or fragment you can do like this :
int mNoOfColumns = Utility.calculateNoOfColumns(getApplicationContext());
............
mGridLayoutManager = new GridLayoutManager(this, mNoOfColumns);
The GridLayoutManager's constructor has an argument spanCount that is
The number of columns in the grid
You can initialize the manager with an integer resource value and provide different values for different screens (i.e. values-w600, values-large, values-land).
I tried #Riten answer and worked funtastic!! But I wasn't happy with the hardcoded "180"
So I modified to this:
public class ColumnQty {
private int width, height, remaining;
private DisplayMetrics displayMetrics;
public ColumnQty(Context context, int viewId) {
View view = View.inflate(context, viewId, null);
view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
width = view.getMeasuredWidth();
height = view.getMeasuredHeight();
displayMetrics = context.getResources().getDisplayMetrics();
}
public int calculateNoOfColumns() {
int numberOfColumns = displayMetrics.widthPixels / width;
remaining = displayMetrics.widthPixels - (numberOfColumns * width);
// System.out.println("\nRemaining\t" + remaining + "\nNumber Of Columns\t" + numberOfColumns);
if (remaining / (2 * numberOfColumns) < 15) {
numberOfColumns--;
remaining = displayMetrics.widthPixels - (numberOfColumns * width);
}
return numberOfColumns;
}
public int calculateSpacing() {
int numberOfColumns = calculateNoOfColumns();
// System.out.println("\nNumber Of Columns\t"+ numberOfColumns+"\nRemaining Space\t"+remaining+"\nSpacing\t"+remaining/(2*numberOfColumns)+"\nWidth\t"+width+"\nHeight\t"+height+"\nDisplay DPI\t"+displayMetrics.densityDpi+"\nDisplay Metrics Width\t"+displayMetrics.widthPixels);
return remaining / (2 * numberOfColumns);
}
}
Where "viewId" is the layout to be used as views in the RecyclerView like in R.layout.item_for_recycler
Not sure though about the impact of View.inflate as I only use it to get the Width, nothing else.
Then on the GridLayoutManager I do:
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, Utility.columnQty(this, R.layout.item_for_recycler));
UPDATE:
I added more lines to the code as I use it to get a minimum width spacing in the Grid.
Calculate spacing:
recyclerPatternsView.addItemDecoration(new GridSpacing(columnQty.calculateSpacing()));
GridSpacing:
public class GridSpacing extends RecyclerView.ItemDecoration {
private final int spacing;
public GridSpacing(int spacing) {
this.spacing = spacing;
}
#Override
public void getItemOffsets(#NonNull Rect outRect, #NonNull View view, #NonNull RecyclerView parent, #NonNull RecyclerView.State state) {
outRect.left = spacing;
outRect.right = spacing;
outRect.bottom = spacing;
outRect.top = spacing;
}
}
A simple Kotlin extension function. Pass the width in DP (same as the overall width in the item xml)
/**
* #param columnWidth - in dp
*/
fun RecyclerView.autoFitColumns(columnWidth: Int) {
val displayMetrics = this.context.resources.displayMetrics
val noOfColumns = ((displayMetrics.widthPixels / displayMetrics.density) / columnWidth).toInt()
this.layoutManager = GridLayoutManager(this.context, noOfColumns)
}
Call it like every other extension...
myRecyclerView.autoFitColumns(200)
public class AutoFitGridLayoutManager extends GridLayoutManager {
private int columnWidth;
private boolean columnWidthChanged = true;
public AutoFitGridLayoutManager(Context context, int columnWidth) {
super(context, 1);
setColumnWidth(columnWidth);
}
public void setColumnWidth(int newColumnWidth) {
if (newColumnWidth > 0 && newColumnWidth != columnWidth) {
columnWidth = newColumnWidth;
columnWidthChanged = true;
}
}
#Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (columnWidthChanged && columnWidth > 0) {
int totalSpace;
if (getOrientation() == VERTICAL) {
totalSpace = getWidth() - getPaddingRight() - getPaddingLeft();
} else {
totalSpace = getHeight() - getPaddingTop() - getPaddingBottom();
}
int spanCount = Math.max(1, totalSpace / columnWidth);
setSpanCount(spanCount);
columnWidthChanged = false;
}
super.onLayoutChildren(recycler, state);
}
}
now you can set LayoutManager to recycle view
here i have set 250px
AutoFitGridLayoutManager layoutManager = new AutoFitGridLayoutManager(this, 250);
recycleView.setLayoutManager(layoutManager)
show the belove image
Constructor new GridLayoutManager(activity, 2) is about GridLayoutManager(Context context, int spanCount) where spanCount is the number of columns in the grid.
Best way is to check window/view width and base on this width count how many spans you want to show.
Use this function, and put margins for cell layout in XML instead using decoration.
public int getNumberOfColumns() {
View view = View.inflate(this, R.layout.row_layout, null);
view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
int width = view.getMeasuredWidth();
int count = getResources().getDisplayMetrics().widthPixels / width;
int remaining = getResources().getDisplayMetrics().widthPixels - width * count;
if (remaining > width - 15)
count++;
return count;
}
Set in recyclerView initialization:
recyclerView.setLayoutManager(new GridLayoutManager(this, 4));

Hide View Propotional to RecyclerView Scrolling

In my android application I have a recyclerview and LinearLayout. LinearLayout lies on the top and recyclerview lies down to the linearlayout. what my requirement is when recyclerview starts scrolls the last item, the linearlayout should start hide in propotion to scroll amount.
I have done a little and it works. but it is not proportional to the scroll amount. here is my code
ScrollListener
public class MyScrollListener extends OnScrollListener {
private LinearLayoutManager mLayoutManager;
public MyScrollListener(LinearLayoutManager manager) {
this.mLayoutManager = manager;
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int visibleItemCount = mLayoutManager.getChildCount();
int totalItemCount = mLayoutManager.getItemCount();
int firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
if (visibleItemCount + firstVisibleItem >= totalItemCount) {
scrollTheShit(dy * -1, ((View) recyclerView.getChildAt(visibleItemCount - 1)).getWidth());
}
}
public void scrollTheShit(int dy, int widthOfLastChild) {}
}
MainActivity
public class MainActivity extends AppCompatActivity {
RecyclerView recyclerView;
LinearLayout linearLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
linearLayout = (LinearLayout) findViewById(R.id.container);
recyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
recyclerView.setHasFixedSize(true);
final LinearLayoutManager mgr = new LinearLayoutManager(this);
recyclerView.setLayoutManager(mgr);
recyclerView.setAdapter(new RecyclerAdapter());
recyclerView.addOnScrollListener(new MyScrollListener(mgr) {
#Override
public void scrollTheShit(int dy, int widthOfLastChild) {
linearLayout.setY(linearLayout.getY() + dy );
}
});
}
private class RecyclerAdapter extends Adapter<RecyclerAdapter.ViewHolder> {
#Override
public int getItemCount() {
return 10;
}
#Override
public void onBindViewHolder(ViewHolder viewHolder,
int position) {
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int position) {
View v = LayoutInflater.from(MainActivity.this)
.inflate(R.layout.child, parent, false);
ViewHolder holder = new ViewHolder(v);
return holder;
}
public class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View arg0) {
super(arg0);
}
}
}
}
I have to hide the top section when the below RecyclerView scrolls
how do I correct this ? anyone please help me
this may help you
QuickReturnRecyclerView.java
import android.content.Context;
import android.os.Build;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;
public class QuickReturnRecyclerView extends RecyclerView {
private static final String TAG = QuickReturnRecyclerView.class.getName();
private View mReturningView;
private static final int STATE_ONSCREEN = 0;
private static final int STATE_OFFSCREEN = 1;
private static final int STATE_RETURNING = 2;
private int mState = STATE_ONSCREEN;
private int mMinRawY = 0;
private int mReturningViewHeight;
private int mGravity = Gravity.BOTTOM;
public QuickReturnRecyclerView(Context context) {
super(context);
init();
}
public QuickReturnRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public QuickReturnRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init(){
}
/**
* The view that should be showed/hidden when scrolling the content.
* Make sure to set the gravity on the this view to either Gravity.Bottom or
* Gravity.TOP and to put it preferable in a FrameLayout.
* #param view Any kind of view
*/
public void setReturningView(View view) {
mReturningView = view;
try {
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mReturningView.getLayoutParams();
mGravity = params.gravity;
} catch (ClassCastException e) {
throw new RuntimeException("The return view need to be put in a FrameLayout");
}
measureView(mReturningView);
mReturningViewHeight = mReturningView.getMeasuredHeight();
addOnScrollListener(new RecyclerScrollListener());
}
private void measureView(View child) {
ViewGroup.LayoutParams p = child.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0, p.width);
int lpHeight = p.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}
private class RecyclerScrollListener extends OnScrollListener {
private int mScrolledY;
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if(mGravity == Gravity.BOTTOM)
mScrolledY += dy;
else if(mGravity == Gravity.TOP)
mScrolledY -= dy;
if(mReturningView == null)
return;
int translationY = 0;
int rawY = mScrolledY;
switch (mState) {
case STATE_OFFSCREEN:
if(mGravity == Gravity.BOTTOM) {
if (rawY >= mMinRawY) {
mMinRawY = rawY;
} else {
mState = STATE_RETURNING;
}
} else if(mGravity == Gravity.TOP) {
if (rawY <= mMinRawY) {
mMinRawY = rawY;
} else {
mState = STATE_RETURNING;
}
}
translationY = rawY;
break;
case STATE_ONSCREEN:
if(mGravity == Gravity.BOTTOM) {
if (rawY > mReturningViewHeight) {
mState = STATE_OFFSCREEN;
mMinRawY = rawY;
}
} else if(mGravity == Gravity.TOP) {
if (rawY < -mReturningViewHeight) {
mState = STATE_OFFSCREEN;
mMinRawY = rawY;
}
}
translationY = rawY;
break;
case STATE_RETURNING:
if(mGravity == Gravity.BOTTOM) {
translationY = (rawY - mMinRawY) + mReturningViewHeight;
if (translationY < 0) {
translationY = 0;
mMinRawY = rawY + mReturningViewHeight;
}
if (rawY == 0) {
mState = STATE_ONSCREEN;
translationY = 0;
}
if (translationY > mReturningViewHeight) {
mState = STATE_OFFSCREEN;
mMinRawY = rawY;
}
} else if(mGravity == Gravity.TOP) {
translationY = (rawY + Math.abs(mMinRawY)) - mReturningViewHeight;
if (translationY > 0) {
translationY = 0;
mMinRawY = rawY - mReturningViewHeight;
}
if (rawY == 0) {
mState = STATE_ONSCREEN;
translationY = 0;
}
if (translationY < -mReturningViewHeight) {
mState = STATE_OFFSCREEN;
mMinRawY = rawY;
}
}
break;
}
/** this can be used if the build is below honeycomb **/
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB) {
TranslateAnimation anim = new TranslateAnimation(0, 0, translationY, translationY);
anim.setFillAfter(true);
anim.setDuration(0);
mReturningView.startAnimation(anim);
} else {
mReturningView.setTranslationY(translationY);
}
}
}
}
activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<yourpackagename.QuickReturnRecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
<LinearLayout
android:id="#+id/linear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="#android:color/darker_gray"
android:orientation="horizontal"
android:padding="15dp">
<Button
android:id="#+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="left|top"
android:layout_weight="1"
android:text="New Button" />
<Button
android:id="#+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|top"
android:layout_weight="1"
android:text="New Button" />
<Button
android:id="#+id/button3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="right|top"
android:layout_weight="1"
android:text="New Button" />
</LinearLayout>
</FrameLayout>
in MainActivity.java just add below lines
mRecyclerView = (QuickReturnRecyclerView) findViewById(R.id.recycler_view);
linear = (LinearLayout) findViewById(R.id.linear);
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(getApplicationContext());
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setReturningView(linear);

How to make sticky section headers (like iOS) in Android?

My specific question is: How I can achieve an effect like this: http://youtu.be/EJm7subFbQI
The bounce effect is not important, but i need the "sticky" effect for the headers. Where do I start?, In what can I base me? I need something that I can implement on API 8 to up.
Thanks.
There are a few solutions that already exist for this problem. What you're describing are section headers and have come to be referred to as sticky section headers in Android.
Sticky List Headers
Sticky Scroll Views
HeaderListView
EDIT: Had some free time to add the code of fully working example. Edited the answer accordingly.
For those who don't want to use 3rd party code (or cannot use it directly, e.g. in Xamarin), this could be done fairly easily by hand.
The idea is to use another ListView for the header. This list view contains only the header items. It will not be scrollable by the user (setEnabled(false)), but will be scrolled from code based on main lists' scrolling. So you will have two lists - headerListview and mainListview, and two corresponding adapters headerAdapter and mainAdapter. headerAdapter only returns section views, while mainAdapter supports two view types (section and item). You will need a method that takes a position in the main list and returns a corresponding position in the sections list.
Main activity
public class MainActivity extends AppCompatActivity {
public static final int TYPE_SECTION = 0;
public static final int TYPE_ITEM = 1;
ListView mainListView;
ListView headerListView;
MainAdapter mainAdapter;
HeaderAdapter headerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mainListView = (ListView)findViewById(R.id.list);
headerListView = (ListView)findViewById(R.id.header);
mainAdapter = new MainAdapter();
headerAdapter = new HeaderAdapter();
headerListView.setEnabled(false);
headerListView.setAdapter(headerAdapter);
mainListView.setAdapter(mainAdapter);
mainListView.setOnScrollListener(new AbsListView.OnScrollListener(){
#Override
public void onScrollStateChanged(AbsListView view, int scrollState){
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// this should return an index in the headers list, based one the index in the main list. The logic for this is highly dependent on your data.
int pos = mainAdapter.getSectionIndexForPosition(firstVisibleItem);
// this makes sure our headerListview shows the proper section (the one on the top of the mainListview)
headerListView.setSelection(pos);
// this makes sure that headerListview is scrolled exactly the same amount as the mainListview
if(mainAdapter.getItemViewType(firstVisibleItem + 1) == TYPE_SECTION){
headerListView.setSelectionFromTop(pos, mainListView.getChildAt(0).getTop());
}
}
});
}
public class MainAdapter extends BaseAdapter{
int count = 30;
#Override
public int getItemViewType(int position){
if((float)position / 10 == (int)((float)position/10)){
return TYPE_SECTION;
}else{
return TYPE_ITEM;
}
}
#Override
public int getViewTypeCount(){ return 2; }
#Override
public int getCount() { return count - 1; }
#Override
public Object getItem(int position) { return null; }
#Override
public long getItemId(int position) { return position; }
public int getSectionIndexForPosition(int position){ return position / 10; }
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = getLayoutInflater().inflate(R.layout.item, parent, false);
position++;
if(getItemViewType(position) == TYPE_SECTION){
((TextView)v.findViewById(R.id.text)).setText("SECTION "+position);
}else{
((TextView)v.findViewById(R.id.text)).setText("Item "+position);
}
return v;
}
}
public class HeaderAdapter extends BaseAdapter{
int count = 5;
#Override
public int getCount() { return count; }
#Override
public Object getItem(int position) { return null; }
#Override
public long getItemId(int position) { return position; }
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = getLayoutInflater().inflate(R.layout.item, parent, false);
((TextView)v.findViewById(R.id.text)).setText("SECTION "+position*10);
return v;
}
}
}
A couple of things to note here. We do not want to show the very first section in the main view list, because it would produce a duplicate (it's already shown in the header). To avoid that, in your mainAdapter.getCount():
return actualCount - 1;
and make sure the first line in your getView() method is
position++;
This way your main list will be rendering all cells but the first one.
Another thing is that you want to make sure your headerListview's height matches the height of the list item. In this example the height is fixed, but it could be tricky if your items height is not set to an exact value in dp. Please refer to this answer for how to address this: https://stackoverflow.com/a/41577017/291688
Main layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin">
<ListView
android:id="#+id/header"
android:layout_width="match_parent"
android:layout_height="48dp"/>
<ListView
android:id="#+id/list"
android:layout_below="#+id/header"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
Item / header layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="48dp">
<TextView
android:id="#+id/text"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
Add this in your app.gradle file
compile 'se.emilsjolander:StickyScrollViewItems:1.1.0'
then my layout, where I have added android:tag ="sticky" to specific views like textview or edittext not LinearLayout, looks like this. It also uses databinding, ignore that.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="temp"
type="com.lendingkart.prakhar.lendingkartdemo.databindingmodel.BusinessDetailFragmentModel" />
<variable
name="presenter"
type="com.lendingkart.prakhar.lendingkartdemo.presenters.BusinessDetailsPresenter" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.lendingkart.prakhar.lendingkartdemo.customview.StickyScrollView
android:id="#+id/sticky_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- scroll view child goes here -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
card_view:cardCornerRadius="5dp"
card_view:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
style="#style/group_view_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/businessdetailtitletextviewbackground"
android:padding="#dimen/activity_horizontal_margin"
android:tag="sticky"
android:text="#string/business_contact_detail" />
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="7dp">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/comapnyLabel"
android:textSize="16sp" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/contactLabel"
android:textSize="16sp" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/emailLabel"
android:textSize="16sp" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/NumberOfEmployee"
android:textSize="16sp" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
card_view:cardCornerRadius="5dp"
card_view:cardUseCompatPadding="true">
<TextView
style="#style/group_view_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/businessdetailtitletextviewbackground"
android:padding="#dimen/activity_horizontal_margin"
android:tag="sticky"
android:text="#string/nature_of_business" />
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
card_view:cardCornerRadius="5dp"
card_view:cardUseCompatPadding="true">
<TextView
style="#style/group_view_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/businessdetailtitletextviewbackground"
android:padding="#dimen/activity_horizontal_margin"
android:tag="sticky"
android:text="#string/taxation" />
</android.support.v7.widget.CardView>
</LinearLayout>
</com.lendingkart.prakhar.lendingkartdemo.customview.StickyScrollView>
</LinearLayout>
</layout>
style group for the textview looks this
<style name="group_view_text" parent="#android:style/TextAppearance.Medium">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textColor">#color/edit_text_color</item>
<item name="android:textSize">16dp</item>
<item name="android:layout_centerVertical">true</item>
<item name="android:textStyle">bold</item>
</style>
and the background for the textview goes like this:(#drawable/businessdetailtitletextviewbackground)
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#color/edit_text_color" />
</shape>
</item>
<item android:bottom="2dp">
<shape android:shape="rectangle">
<solid android:color="#color/White" />
</shape>
</item>
</layer-list>
For those looking for a solution in 2020, I have quickly created a solution extending the Layout Manager from ruslansharipov project (Sticky Header) and combining it whith the RecycleView Adapter from lisawray Groupie project (Expandable RecycleView).
You can see my example here
Result Here
You can reach this effect using SuperSLiM library. It provides you a LayoutManager for RecyclerView with interchangeable linear, grid, and staggered displays of views.
A good demo is located in github repository
It is simply to get such result
app:slm_headerDisplay="inline|sticky"
or
app:slm_headerDisplay="sticky"
I have used one special class to achieve listview like iPhone.
You can find example with source code here. https://demonuts.com/android-recyclerview-sticky-header-like-iphone/
This class which has updated listview is as
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.ListView;
import android.widget.RelativeLayout;
public class HeaderListView extends RelativeLayout {
// TODO: Handle listViews with fast scroll
// TODO: See if there are methods to dispatch to mListView
private static final int FADE_DELAY = 1000;
private static final int FADE_DURATION = 2000;
private InternalListView mListView;
private SectionAdapter mAdapter;
private RelativeLayout mHeader;
private View mHeaderConvertView;
private FrameLayout mScrollView;
private AbsListView.OnScrollListener mExternalOnScrollListener;
public HeaderListView(Context context) {
super(context);
init(context, null);
}
public HeaderListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
mListView = new InternalListView(getContext(), attrs);
LayoutParams listParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
listParams.addRule(ALIGN_PARENT_TOP);
mListView.setLayoutParams(listParams);
mListView.setOnScrollListener(new HeaderListViewOnScrollListener());
mListView.setVerticalScrollBarEnabled(false);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (mAdapter != null) {
mAdapter.onItemClick(parent, view, position, id);
}
}
});
addView(mListView);
mHeader = new RelativeLayout(getContext());
LayoutParams headerParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
headerParams.addRule(ALIGN_PARENT_TOP);
mHeader.setLayoutParams(headerParams);
mHeader.setGravity(Gravity.BOTTOM);
addView(mHeader);
// The list view's scroll bar can be hidden by the header, so we display our own scroll bar instead
Drawable scrollBarDrawable = getResources().getDrawable(R.drawable.scrollbar_handle_holo_light);
mScrollView = new FrameLayout(getContext());
LayoutParams scrollParams = new LayoutParams(scrollBarDrawable.getIntrinsicWidth(), LayoutParams.MATCH_PARENT);
scrollParams.addRule(ALIGN_PARENT_RIGHT);
scrollParams.rightMargin = (int) dpToPx(2);
mScrollView.setLayoutParams(scrollParams);
ImageView scrollIndicator = new ImageView(context);
scrollIndicator.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
scrollIndicator.setImageDrawable(scrollBarDrawable);
scrollIndicator.setScaleType(ScaleType.FIT_XY);
mScrollView.addView(scrollIndicator);
mScrollView.setVisibility(INVISIBLE);
addView(mScrollView);
}
public void setAdapter(SectionAdapter adapter) {
mAdapter = adapter;
mListView.setAdapter(adapter);
}
public void setOnScrollListener(AbsListView.OnScrollListener l) {
mExternalOnScrollListener = l;
}
private class HeaderListViewOnScrollListener implements AbsListView.OnScrollListener {
private int previousFirstVisibleItem = -1;
private int direction = 0;
private int actualSection = 0;
private boolean scrollingStart = false;
private boolean doneMeasuring = false;
private int lastResetSection = -1;
private int nextH;
private int prevH;
private View previous;
private View next;
private AlphaAnimation fadeOut = new AlphaAnimation(1f, 0f);
private boolean noHeaderUpToHeader = false;
private boolean didScroll = false;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (mExternalOnScrollListener != null) {
mExternalOnScrollListener.onScrollStateChanged(view, scrollState);
}
didScroll = true;
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (mExternalOnScrollListener != null) {
mExternalOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
if (!didScroll) {
return;
}
firstVisibleItem -= mListView.getHeaderViewsCount();
if (firstVisibleItem < 0) {
mHeader.removeAllViews();
return;
}
updateScrollBar();
if (visibleItemCount > 0 && firstVisibleItem == 0 && mHeader.getChildAt(0) == null) {
addSectionHeader(0);
lastResetSection = 0;
}
int realFirstVisibleItem = getRealFirstVisibleItem(firstVisibleItem, visibleItemCount);
if (totalItemCount > 0 && previousFirstVisibleItem != realFirstVisibleItem) {
direction = realFirstVisibleItem - previousFirstVisibleItem;
actualSection = mAdapter.getSection(realFirstVisibleItem);
boolean currIsHeader = mAdapter.isSectionHeader(realFirstVisibleItem);
boolean prevHasHeader = mAdapter.hasSectionHeaderView(actualSection - 1);
boolean nextHasHeader = mAdapter.hasSectionHeaderView(actualSection + 1);
boolean currHasHeader = mAdapter.hasSectionHeaderView(actualSection);
boolean currIsLast = mAdapter.getRowInSection(realFirstVisibleItem) == mAdapter.numberOfRows(actualSection) - 1;
boolean prevHasRows = mAdapter.numberOfRows(actualSection - 1) > 0;
boolean currIsFirst = mAdapter.getRowInSection(realFirstVisibleItem) == 0;
boolean needScrolling = currIsFirst && !currHasHeader && prevHasHeader && realFirstVisibleItem != firstVisibleItem;
boolean needNoHeaderUpToHeader = currIsLast && currHasHeader && !nextHasHeader && realFirstVisibleItem == firstVisibleItem && Math.abs(mListView.getChildAt(0).getTop()) >= mListView.getChildAt(0).getHeight() / 2;
noHeaderUpToHeader = false;
if (currIsHeader && !prevHasHeader && firstVisibleItem >= 0) {
resetHeader(direction < 0 ? actualSection - 1 : actualSection);
} else if ((currIsHeader && firstVisibleItem > 0) || needScrolling) {
if (!prevHasRows) {
resetHeader(actualSection-1);
}
startScrolling();
} else if (needNoHeaderUpToHeader) {
noHeaderUpToHeader = true;
} else if (lastResetSection != actualSection) {
resetHeader(actualSection);
}
previousFirstVisibleItem = realFirstVisibleItem;
}
if (scrollingStart) {
int scrolled = realFirstVisibleItem >= firstVisibleItem ? mListView.getChildAt(realFirstVisibleItem - firstVisibleItem).getTop() : 0;
if (!doneMeasuring) {
setMeasurements(realFirstVisibleItem, firstVisibleItem);
}
int headerH = doneMeasuring ? (prevH - nextH) * direction * Math.abs(scrolled) / (direction < 0 ? nextH : prevH) + (direction > 0 ? nextH : prevH) : 0;
mHeader.scrollTo(0, -Math.min(0, scrolled - headerH));
if (doneMeasuring && headerH != mHeader.getLayoutParams().height) {
LayoutParams p = (LayoutParams) (direction < 0 ? next.getLayoutParams() : previous.getLayoutParams());
p.topMargin = headerH - p.height;
mHeader.getLayoutParams().height = headerH;
mHeader.requestLayout();
}
}
if (noHeaderUpToHeader) {
if (lastResetSection != actualSection) {
addSectionHeader(actualSection);
lastResetSection = actualSection + 1;
}
mHeader.scrollTo(0, mHeader.getLayoutParams().height - (mListView.getChildAt(0).getHeight() + mListView.getChildAt(0).getTop()));
}
}
private void startScrolling() {
scrollingStart = true;
doneMeasuring = false;
lastResetSection = -1;
}
private void resetHeader(int section) {
scrollingStart = false;
addSectionHeader(section);
mHeader.requestLayout();
lastResetSection = section;
}
private void setMeasurements(int realFirstVisibleItem, int firstVisibleItem) {
if (direction > 0) {
nextH = realFirstVisibleItem >= firstVisibleItem ? mListView.getChildAt(realFirstVisibleItem - firstVisibleItem).getMeasuredHeight() : 0;
}
previous = mHeader.getChildAt(0);
prevH = previous != null ? previous.getMeasuredHeight() : mHeader.getHeight();
if (direction < 0) {
if (lastResetSection != actualSection - 1) {
addSectionHeader(Math.max(0, actualSection - 1));
next = mHeader.getChildAt(0);
}
nextH = mHeader.getChildCount() > 0 ? mHeader.getChildAt(0).getMeasuredHeight() : 0;
mHeader.scrollTo(0, prevH);
}
doneMeasuring = previous != null && prevH > 0 && nextH > 0;
}
private void updateScrollBar() {
if (mHeader != null && mListView != null && mScrollView != null) {
int offset = mListView.computeVerticalScrollOffset();
int range = mListView.computeVerticalScrollRange();
int extent = mListView.computeVerticalScrollExtent();
mScrollView.setVisibility(extent >= range ? View.INVISIBLE : View.VISIBLE);
if (extent >= range) {
return;
}
int top = range == 0 ? mListView.getHeight() : mListView.getHeight() * offset / range;
int bottom = range == 0 ? 0 : mListView.getHeight() - mListView.getHeight() * (offset + extent) / range;
mScrollView.setPadding(0, top, 0, bottom);
fadeOut.reset();
fadeOut.setFillBefore(true);
fadeOut.setFillAfter(true);
fadeOut.setStartOffset(FADE_DELAY);
fadeOut.setDuration(FADE_DURATION);
mScrollView.clearAnimation();
mScrollView.startAnimation(fadeOut);
}
}
private void addSectionHeader(int actualSection) {
View previousHeader = mHeader.getChildAt(0);
if (previousHeader != null) {
mHeader.removeViewAt(0);
}
if (mAdapter.hasSectionHeaderView(actualSection)) {
mHeaderConvertView = mAdapter.getSectionHeaderView(actualSection, mHeaderConvertView, mHeader);
mHeaderConvertView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
mHeaderConvertView.measure(MeasureSpec.makeMeasureSpec(mHeader.getWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
mHeader.getLayoutParams().height = mHeaderConvertView.getMeasuredHeight();
mHeaderConvertView.scrollTo(0, 0);
mHeader.scrollTo(0, 0);
mHeader.addView(mHeaderConvertView, 0);
} else {
mHeader.getLayoutParams().height = 0;
mHeader.scrollTo(0, 0);
}
mScrollView.bringToFront();
}
private int getRealFirstVisibleItem(int firstVisibleItem, int visibleItemCount) {
if (visibleItemCount == 0) {
return -1;
}
int relativeIndex = 0, totalHeight = mListView.getChildAt(0).getTop();
for (relativeIndex = 0; relativeIndex < visibleItemCount && totalHeight < mHeader.getHeight(); relativeIndex++) {
totalHeight += mListView.getChildAt(relativeIndex).getHeight();
}
int realFVI = Math.max(firstVisibleItem, firstVisibleItem + relativeIndex - 1);
return realFVI;
}
}
public ListView getListView() {
return mListView;
}
public void addHeaderView(View v) {
mListView.addHeaderView(v);
}
private float dpToPx(float dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics());
}
protected class InternalListView extends ListView {
public InternalListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected int computeVerticalScrollExtent() {
return super.computeVerticalScrollExtent();
}
#Override
protected int computeVerticalScrollOffset() {
return super.computeVerticalScrollOffset();
}
#Override
protected int computeVerticalScrollRange() {
return super.computeVerticalScrollRange();
}
}
}
XML usage
<?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:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.parsaniahardik.listview_stickyheader_ios.HeaderListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/lv">
</com.example.parsaniahardik.listview_stickyheader_ios.HeaderListView>

Categories

Resources