ksoichiro / Android-ObservableScrollView

Android library to observe scroll events on scrollable views.
http://ksoichiro.github.io/Android-ObservableScrollView/
Apache License 2.0
9.65k stars 2.06k forks source link

Toolbar and SlidingTabLayout is overlaying ObservableRecyclerView #198

Open jimitpatel opened 9 years ago

jimitpatel commented 9 years ago

First of all a heartily gratitude for all of your hard works. I liked this library very much. Currently I am trying to figure out how it is working. And got stucked in between. I am using ViewPagerTabActivity and ViewPagerRecyclerViewFragment example of yours.

ViewPagerTabActivity

public class ViewPagerTabActivity extends AppCompatActivity implements ObservableScrollViewCallbacks {

    private static final String TAG = ViewPagerTabActivity.class.getSimpleName();
    private static final boolean IS_DEBUG = true;

    private Context mContext;

    private View mHeaderView;
    private View mToolbarView;
    private ViewPager mPager;
    private NavigationAdapter mPagerAdapter;
    private SlidingTabLayout slidingTabLayout;

    private int mBaseTranslationY;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_viewpagertab);

        mContext = ViewPagerTabActivity.this;

        setSupportActionBar(((Toolbar) findViewById(R.id.toolbar)));

        mHeaderView = findViewById(R.id.header);
        ViewCompat.setElevation(mHeaderView, getResources().getDimension(R.dimen.toolbar_elevation));
        mToolbarView = findViewById(R.id.toolbar);
        mPager = (ViewPager) findViewById(R.id.pager);
        mPagerAdapter = new NavigationAdapter(getSupportFragmentManager(), new ProductDBAdapter(mContext));
        mPager.setAdapter(mPagerAdapter);

        slidingTabLayout = (SlidingTabLayout) findViewById(R.id.sliding_tabs);
        slidingTabLayout.setCustomTabView(R.layout.tab_indicator, android.R.id.text1);
        slidingTabLayout.setSelectedIndicatorColors(getResources().getColor(R.color.float_color));
        slidingTabLayout.setDistributeEvenly(true);
        slidingTabLayout.setViewPager(mPager);
        slidingTabLayout.setPadding(0, mHeaderView.getHeight(), 0, 0);

        // When the page is selected, other fragments' scrollY should be adjusted
        // according to the toolbar status(shown/hidden)
        slidingTabLayout.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            @Override
            public void onPageSelected(int position) {

            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
        propagateToolbarState(toolbarIsShown());
    }

    @Override
    public void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging) {
        if(dragging) {
            int toolbarHeight = mToolbarView.getHeight();
            float currentHeaderTranslationY = ViewHelper.getTranslationY(mHeaderView);
            if(firstScroll) {
                if(-toolbarHeight < currentHeaderTranslationY)
                    mBaseTranslationY = scrollY;
            }
            float headerTranslationY = ScrollUtils.getFloat(-(scrollY - mBaseTranslationY), -toolbarHeight, 0);
            ViewPropertyAnimator.animate(mHeaderView).cancel();
            ViewHelper.setTranslationY(mHeaderView, headerTranslationY);
//            if (Config.Build.IS_DEBUG && IS_DEBUG)
//                Log.d(TAG, "onScrollChanged toolbarHeight|firstScroll|mBaseTranslationY|headerTranslationY: " + toolbarHeight + "|" + firstScroll + "|" + "|" + mBaseTranslationY + "|" +headerTranslationY);
        }
    }

    @Override
    public void onDownMotionEvent() {

    }

    @Override
    public void onUpOrCancelMotionEvent(ScrollState scrollState) {
        mBaseTranslationY = 0;

        Fragment fragment = getCurrentFragment();
        if(fragment == null)
            return;
        View view = fragment.getView();
        if(view == null)
            return;

        // ObservableXxxViews have same API
        // but currently they don't have any common interfaces.
        adjustToolbar(scrollState, view);
    }

    private void adjustToolbar(ScrollState scrollState, View view) {
        int toolbarHeight = mToolbarView.getHeight();
        final Scrollable scrollView = (Scrollable) view.findViewById(R.id.scroll);

        if(scrollView == null)
            return;

        int scrollY = scrollView.getCurrentScrollY();
        if(scrollState == ScrollState.DOWN){
            showToolbar();
        } else if(scrollState == ScrollState.UP){
            if(toolbarHeight <= scrollY){
                hideToolbar();
            } else {
                showToolbar();
            }
        } else {
            // Even if onScrollChanged occurs without scrollY changing, toolbar should be adjusted
            if (toolbarIsShown() || toolbarIsHidden()) {
                // Toolbar is completely moved, so just keep its state
                // and propagate it to other pages
                propagateToolbarState(toolbarIsShown());
            } else {
                // Toolbar is moving but doesn't know which to move:
                // you can change this to hideToolbar()
                showToolbar();
            }
        }
    }

    private Fragment getCurrentFragment() {
        return mPagerAdapter.getItemAt(mPager.getCurrentItem());
    }

    private void propagateToolbarState(boolean isShown) {
        int toolbarHeight = mToolbarView.getHeight();

        //Set scrollY for the fragments that are not created yet
        mPagerAdapter.setScrollY(isShown ? 0 : toolbarHeight);

        // Set scrollY for active fragments
        for(int i = 0; i < mPagerAdapter.getCount(); i++) {
            // Skip current item
            if(i == mPager.getCurrentItem())
                continue;

            // Skip destroyed or not created item
            Fragment f = mPagerAdapter.getItemAt(i);
            if(f == null)
                continue;

            View view = f.getView();
            if(view == null)
                continue;

            propagateToolbarState(isShown, view, toolbarHeight);
        }
    }

    private void propagateToolbarState(boolean isShown, View view, int toolbarHeight) {
        Scrollable scrollView = (Scrollable) view.findViewById(R.id.scroll);
        if(scrollView == null)
            return;

        if(isShown){
            //Scroll Up
            if(0 < scrollView.getCurrentScrollY())
                scrollView.scrollVerticallyTo(0);
        } else {
            //Scroll Down (to hide listitem_stock_header)
            if(scrollView.getCurrentScrollY() < toolbarHeight) {
                scrollView.scrollVerticallyTo(toolbarHeight);
            }
        }
    }

    private boolean toolbarIsShown() {
        return ViewHelper.getTranslationY(mHeaderView) == 0;
    }

    private boolean toolbarIsHidden() {
        return ViewHelper.getTranslationY(mHeaderView) == -mToolbarView.getHeight();
    }

    private void showToolbar() {
        float headerTranslationY = ViewHelper.getTranslationY(mHeaderView);
        if(headerTranslationY != 0) {
            ViewPropertyAnimator.animate(mHeaderView).cancel();
            ViewPropertyAnimator.animate(mHeaderView).translationY(0).setDuration(200).start();
        }
        propagateToolbarState(true);
    }

    private void hideToolbar() {
        float headerTranslationY = ViewHelper.getTranslationY(mHeaderView);
        int toolbarHeight = mToolbarView.getHeight();
        if (headerTranslationY != -toolbarHeight) {
            ViewPropertyAnimator.animate(mHeaderView).cancel();
            ViewPropertyAnimator.animate(mHeaderView).translationY(-toolbarHeight).setDuration(200).start();
        }
        propagateToolbarState(false);
    }
}

Even it's XML is same as given in sample -

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!--
    Padding for ViewPager must be set outside the ViewPager itself
    because with listitem_stock_header, EdgeEffect of ViewPager become strange.
    -->
    <FrameLayout
        android:id="@+id/pager_wrapper"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v4.view.ViewPager
            android:id="@+id/pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>

    <LinearLayout
        android:id="@+id/header"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:orientation="vertical">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            style="@style/Toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="?attr/actionBarSize"
            app:popupTheme="@style/Theme.AppCompat.Light.DarkActionBar" />

        <com.groshop.jaguar.app.ui.widget.SlidingTabLayout
            android:id="@+id/sliding_tabs"
            android:layout_width="match_parent"
            android:layout_height="@dimen/tab_height" />
    </LinearLayout>
</FrameLayout>

ViewPagerRecyclerViewFragment

public class ViewPagerRecyclerViewFragment extends Fragment {

    private static final String TAG = ViewPagerRecyclerViewFragment.class.getSimpleName();
    private static final boolean IS_DEBUG = true;
    public static final String  ARG_INITIAL_POSITION = "ARG_INITIAL_POSITION";

    private ArrayList<StockModel> stockModelArrayList;

    private StockRecyclerViewAdapter mAdapter;

    public static ViewPagerRecyclerViewFragment getInstance(ArrayList<StockModel> stockModelArrayList) {
        ViewPagerRecyclerViewFragment fragment = new ViewPagerRecyclerViewFragment();
        Bundle bundle = new Bundle();
        bundle.putParcelableArrayList(Config.Bundle.STOCK_LIST, stockModelArrayList);
        fragment.setArguments(bundle);
        return fragment;
    }

    public static ViewPagerRecyclerViewFragment getInstance(ArrayList<StockModel> stockModelArrayList, int initalPosition) {
        ViewPagerRecyclerViewFragment fragment = new ViewPagerRecyclerViewFragment();
        Bundle bundle = new Bundle();
        bundle.putParcelableArrayList(Config.Bundle.STOCK_LIST, stockModelArrayList);
        bundle.putInt(ARG_INITIAL_POSITION, initalPosition);
        fragment.setArguments(bundle);
        return fragment;
    }

    public ViewPagerRecyclerViewFragment() {}

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initVals();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_viewpager_recyclerview, container, false);

        Activity parentActivity = getActivity();
        final ObservableRecyclerView recyclerView = (ObservableRecyclerView) view.findViewById(R.id.scroll);
        recyclerView.setLayoutManager(new LinearLayoutManager(parentActivity));
        recyclerView.setHasFixedSize(false);
        mAdapter = new StockRecyclerViewAdapter(parentActivity, stockModelArrayList);
        recyclerView.setAdapter(mAdapter);
//        setDummyDataWithHeader(recyclerView, headerView);

        if (parentActivity instanceof ObservableScrollViewCallbacks) {
            // Scroll to the specified offset after layout

            Bundle args = getArguments();
            if (args != null && args.containsKey(ARG_INITIAL_POSITION)) {
                final int initialPosition = args.getInt(ARG_INITIAL_POSITION, 0);
                ScrollUtils.addOnGlobalLayoutListener(recyclerView, new Runnable() {
                    @Override
                    public void run() {
                        recyclerView.scrollVerticallyToPosition(initialPosition);
                    }
                });
            }

            // TouchInterceptionViewGroup should be a parent view other than ViewPager.
            // This is a workaround for the issue #117:
            // https://github.com/ksoichiro/Android-ObservableScrollView/issues/117
            recyclerView.setTouchInterceptionViewGroup((ViewGroup) parentActivity.findViewById(R.id.root));
            recyclerView.setScrollViewCallbacks((ObservableScrollViewCallbacks) parentActivity);
        }
        return view;
    }

    private void initVals() {
        Bundle bundle = getArguments();
        if(null != bundle && bundle.containsKey(Config.Bundle.STOCK_LIST)) {
            stockModelArrayList = (ArrayList<StockModel>) bundle.get(Config.Bundle.STOCK_LIST);
        } else
            stockModelArrayList = new ArrayList<>();
    }
}

And XML for fragment is almost similar except few changes.

<com.github.ksoichiro.android.observablescrollview.ObservableRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scroll"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginTop="?attr/actionBarSize"
    android:fillViewport="true"
    android:overScrollMode="never"
    android:scrollIndicators="right"
    android:scrollbarAlwaysDrawVerticalTrack="true"
    android:scrollbarStyle="insideOverlay"
    android:scrollbarThumbVertical="@color/primary_text_disable_color"
    android:scrollbars="vertical" />

If observablerecycler's adapter has some problem then here it goes -

StockRecyclerViewAdapter

public class StockRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static final String TAG = StockRecyclerViewAdapter.class.getSimpleName();
    private static final boolean IS_DEBUG = true;

    private static final int VIEW_TYPE_HEADER = 0;
    private static final int VIEW_TYPE_ITEM = 1;

    private LayoutInflater mInflater;
    private ArrayList<StockModel> mItems;
    private Context context;

    public StockRecyclerViewAdapter(Context context, ArrayList<StockModel> items) {
        this.context = context;
        mInflater = LayoutInflater.from(context);
        mItems = items;
    }

    @Override
    public int getItemCount() {
        return mItems.size();
    }

    @Override
    public int getItemViewType(int position) {
        return (position == 0) ? VIEW_TYPE_HEADER : VIEW_TYPE_ITEM;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == VIEW_TYPE_HEADER) {
            return new HeaderViewHolder(mInflater.inflate(R.layout.listitem_stock_header, parent, false), context);
        } else {
            return new ItemViewHolder(mInflater.inflate(R.layout.listitem_stock, parent, false), context);
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        TextView lblDescription;
        SquareNetworkImageView imgProduct;
        CardView container;
        if (viewHolder instanceof ItemViewHolder) {
            lblDescription = ((ItemViewHolder) viewHolder).textView;
            imgProduct = ((ItemViewHolder) viewHolder).img;
            container = ((ItemViewHolder) viewHolder).container;
        } else {
            lblDescription = ((HeaderViewHolder) viewHolder).textView;
            imgProduct = ((HeaderViewHolder) viewHolder).img;
            container = ((HeaderViewHolder) viewHolder).container;
        }

        lblDescription.setText(mItems.get(position).getProductName());

        if (null != mItems.get(position).getImageServer()
                && Patterns.WEB_URL.matcher(mItems.get(position).getImageServer()).matches()) {

            imgProduct.setImageUrl(mItems.get(position).getImageServer(),
                    Jaguar.getInstance().getImageLoader());
        }

        container.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                StockModel data = (StockModel) v.getTag();
                // TODO display overlay for changing stock
            }
        });

        container.setTag(mItems.get(position));
    }

    static class HeaderViewHolder extends RecyclerView.ViewHolder {
        TextView textView;
        SquareNetworkImageView img;
        CardView container;

        public HeaderViewHolder(View view, Context context) {
            super(view);
            textView = (TextView) view.findViewById(android.R.id.text1);
            textView.setTextColor(context.getResources().getColor(R.color.gray_blue));

            img = (SquareNetworkImageView) view.findViewById(R.id.img_product);
            container = (CardView) view.findViewById(R.id.cv_container);
        }
    }

    static class ItemViewHolder extends RecyclerView.ViewHolder {
        TextView textView;
        SquareNetworkImageView img;
        CardView container;

        public ItemViewHolder(View view, Context context) {
            super(view);
            textView = (TextView) view.findViewById(android.R.id.text1);
            textView.setTextColor(context.getResources().getColor(R.color.gray_blue));

            img = (SquareNetworkImageView) view.findViewById(R.id.img_product);
            container = (CardView) view.findViewById(R.id.cv_container);
        }
    }
}

It has two different layouts -

listitem_stock_header

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <View
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:minHeight="?attr/actionBarSize" />

    <android.support.v7.widget.CardView
        android:id="@+id/cv_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:minHeight="?attr/actionBarSize"
        app:cardCornerRadius="2dp"
        app:cardMaxElevation="2dp"
        app:cardUseCompatPadding="true"
        app:contentPaddingBottom="5dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_vertical"
            android:orientation="horizontal"
            android:weightSum="4">

            <com.groshop.jaguar.app.ui.widget.SquareNetworkImageView
                android:id="@+id/img_product"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="0.75"
                android:padding="?android:attr/listPreferredItemPaddingLeft"
                android:scaleType="fitXY" />

            <TextView
                android:id="@android:id/text1"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="3.25"
                android:gravity="center_vertical"
                android:minHeight="?android:attr/listPreferredItemHeightSmall"
                android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
                android:paddingRight="?android:attr/listPreferredItemPaddingRight"
                android:textAppearance="?android:attr/textAppearanceListItemSmall" />
        </LinearLayout>
    </android.support.v7.widget.CardView>
</LinearLayout>

And other is ### listitem_stock ###

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/cv_container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:minHeight="?attr/actionBarSize"
    app:cardCornerRadius="2dp"
    app:cardMaxElevation="2dp"
    app:cardUseCompatPadding="true"
    app:contentPaddingBottom="5dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:weightSum="4">

        <com.groshop.jaguar.app.ui.widget.SquareNetworkImageView
            android:id="@+id/img_product"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.75"
            android:padding="?android:attr/listPreferredItemPaddingLeft"
            android:scaleType="fitXY" />

        <TextView
            android:id="@android:id/text1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="3.25"
            android:gravity="center_vertical"
            android:minHeight="?android:attr/listPreferredItemHeightSmall"
            android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
            android:paddingRight="?android:attr/listPreferredItemPaddingRight"
            android:textAppearance="?android:attr/textAppearanceListItemSmall" />
    </LinearLayout>
</android.support.v7.widget.CardView>

And this is what I get as result -

screenshot_2015-08-30-01-02-23 screenshot_2015-08-30-01-02-14

If you see there are two images. One is at initial state (observe red markings) which is showing tool bar whereas other is toolbar in hidden state.

Issue is that, first 2 objects of observablerecyclerview is always remaining in hidden state, no matter what.

Thanks! :)

vikalppatelce commented 7 years ago

@jimitpatel Found any solution for this?

jimitpatel commented 7 years ago

@vikalppatelce I have stopped using this library and using core AOSP. It's mixture of CoordinatingLayout, AppBarLayout, ViewPager

vikalppatelce commented 7 years ago

@jimitpatel Cool Thanks for reverting! :+1: