Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a locked ScrollView in Android

I'm trying to implement a ScrollView in Android that doesn't scroll when adding an item above the current scroll position.

The default implementation of ScrollView behaves as following:

Adding an item above the current scroll position: enter image description here

Adding an item below the current scroll position: enter image description here

How can I "lock" the ScrollView prior to adding an item above the current scroll position?

This is my layout file, I've currently overridden both the ScrollView and LinearLayout, but haven't made any alterations yet.

<LinearLayout
      android:layout_height="fill_parent"
      android:orientation="vertical"
      android:layout_width="fill_parent">

    <LinearLayout
        android:id="@+id/LinearLayout02"
        android:layout_height="wrap_content"
        android:layout_width="fill_parent"
        android:layout_alignParentBottom="true">
        <Button
            android:id="@+id/Button02"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1" android:text="Add To Top"
            android:onClick="addToStart">
        </Button>
        <Button
            android:id="@+id/Button03"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Add to End"
            android:onClick="addToEnd">
        </Button>
    </LinearLayout>
    <com.poc.scroller.locable.lockablescrollerpoc.LockedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:fillViewport="true"
        android:scrollbarAlwaysDrawVerticalTrack="true"
        android:verticalScrollbarPosition="right"
        android:fadeScrollbars="false"
        android:background="@color/scrollColor">

        <com.poc.scroller.locable.lockablescrollerpoc.LockedLinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:id="@+id/Container">
        </com.poc.scroller.locable.lockablescrollerpoc.LockedLinearLayout>

    </com.poc.scroller.locable.lockablescrollerpoc.LockedScrollView>
</LinearLayout>
</android.support.constraint.ConstraintLayout>

Example Source Code: https://github.com/Amaros90/android-lockable-scroller-poc

Thank you!

like image 824
Amaros Avatar asked Jul 10 '17 08:07

Amaros


3 Answers

You can easily get the opposite behavior by using addOnLayoutChangeListener of your LinearLayout and reset the ScrollView's ScrollY. Here is the implementation in your ScrollViewActivity.onCreate

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.scrollview);

    _linearLayout = (LockedLinearLayout)findViewById(R.id.Container);
    _scrollView = (LockedScrollView)findViewById(R.id.ScrollView);
    _layoutInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    _linearLayout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
        @Override
        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
            _scrollView.scrollTo(0, _scrollView.getScrollY() + (bottom - oldBottom ));
        }
    });

    addExampleImage(10, _linearLayout);
}

Then you can easily flag the addToEnd function or check where the child was added to avoid changing scroll when some child is added to the bottom.

like image 105
MTZ4 Avatar answered Oct 16 '22 21:10

MTZ4


You can try to use RecyclerView and implement onDataSetChanged(). Then detect whether add to TOP or add to END button was pressed. Then use scrollToPositionWithOffset(int, int) to manage the scroll.

For example:

//Scroll item 3 to 20 pixels from the top
linearLayoutManager.scrollToPositionWithOffset(3, 20);

For restoring the scroll position of a RecyclerView, this is how to save the scroll positions (the two arguments for the method scrollToPositionWithOffset(int, int)):

int index = linearLayoutManager.findFirstVisibleItemPosition(); 
View v = linearLayoutManager.getChildAt(0); 
int top = (v == null) ? 0 : (v.getTop() - linearLayoutManager.getPaddingTop())
like image 1
UmAnusorn Avatar answered Oct 16 '22 22:10

UmAnusorn


Implementing this worked for me. You can check out the example app I added in the original question.

public class LockableScrollView extends ScrollView {

    private boolean _enabled = true;

    public LockableScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        super.setFillViewport(true);
    }

    @Override
    public void addView(View layout, ViewGroup.LayoutParams params) {
        super.addView(layout, params);

        ((ViewGroup)layout).setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
            @Override
            public void onChildViewAdded(View layout, View item) {
                if (_enabled) {
                    item.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
                        @Override
                        public void onLayoutChange(View item, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                        item.removeOnLayoutChangeListener(this);

                        int scrollViewY = ((LockableScrollView)item.getParent().getParent()).getScrollY();
                        int layoutPosition = ((View)item.getParent()).getTop();
                        boolean shouldScroll = item.getTop() + layoutPosition <= scrollViewY || item.getBottom() + getTop() <= scrollViewY;



                        if (shouldScroll) {
                            final int childViewHeight = item.getHeight();

                            ((View)item.getParent()).addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
                                @Override
                                public void onLayoutChange(View layout, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                                    layout.removeOnLayoutChangeListener(this);

                                    LockableScrollView scrollView = ((LockableScrollView)layout.getParent());
                                    scrollView.scrollTo(scrollView.getScrollX(), scrollView.getScrollY() + childViewHeight);
                                }
                            });
                        }
                        }
                    });
                }
            }

            @Override
            public void onChildViewRemoved(View layout, View item) {
                if (_enabled) {
                    int scrollViewY = ((LockableScrollView)layout.getParent()).getScrollY();
                    int layoutPosition = layout.getTop();
                    boolean shouldScroll = item.getTop() + layoutPosition <= scrollViewY || item.getBottom() + getTop() <= scrollViewY;


                    if (shouldScroll) {
                        final int childViewHeight = item.getHeight();

                        layout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
                            @Override
                            public void onLayoutChange(View layout, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                                layout.removeOnLayoutChangeListener(this);

                                LockableScrollView scrollView = ((LockableScrollView)layout.getParent());
                                scrollView.scrollTo(scrollView.getScrollX(), scrollView.getScrollY() - childViewHeight);
                            }
                        });
                    }
                }
            }
        });
    }
}
like image 1
Amaros Avatar answered Oct 16 '22 20:10

Amaros