Search code examples
androidscrollbarandroid-custom-view

Scroll by only using the bar not the child view itself


In my app I have a custom view like below image,

Audio Visualizer

This is an audio visualiser, which grow across X axis upon zoom in. So I need it inside a scrollview to scroll through the audio graph. The layout is currently done like below,

<HorizontalScrollView
        android:id="@+id/fileWaveViewScroll"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fillViewport="true"
        style="@style/CustomScrollbar">

        <com.bluehub.fastmixer.common.views.FileWaveView
            android:id="@+id/fileWaveView"
            android:layout_width="wrap_content"
            android:layout_height="120dp"
            app:audioFileUiState="@{audioFileUiState}"
            app:samplesReader="@{eventListener.readSamplesCallbackWithIndex(audioFileUiState.path)}"
            app:fileWaveViewStore="@{fileWaveViewStore}"
            tools:layout_height="120dp"/>
</HorizontalScrollView>

Custom view FileWaveView is extended from a LinearLayout.

Now I want a the scrolling behaviour such that when I am scrolling inside the custom view (audio visualiser graph), the scrollview should not scroll. But when I am scrolling by touching the bar itself, then the scrollbar will scroll it's content.

I want this behaviour because I want the scroll gesture to be used for some other action inside the visualiser graph (to adjust a segment selector width, which I will integrate later).

Please suggest me how can I make this scrollbar scrolling pattern, while the view will be scrolled through the bar only. Should I refrain from using HorizontalScrollBar and make own scrolling behaviour inside of the custom view itself?


Solution

  • TL;DR

    There isn't anything that would help you disable your scrolling on HorizontalScrollView with slide event and only do it when ScrollBar is used. The reason for this is that ScrollBar isn't a separate view from HorizontalScrollView and it doesn't have any listeners implemented on itself. There is an option for third-party libraries to work with audio visualization or audio files or to implement this workaround I posted below. Not quite sure if it is going to work at first because it needs a lot of work but maybe is worth trying.


    I am not sure that there is something like that in HorizontalScrollView methods. But there are some options which can help you do this. I couldn't find anything, like a tutorial, to make this happen for you but I might make this work with some workarounds. First, there is this:

    You cannot disable the scrolling of a ScrollView. You would need to extend to ScrollView and override the onTouchEvent method to return false when some condition is matched.

    So, you need to first do this in order to prevent the user from scrolling the view. This will disable scrolling on touch and that's what you need in this case. Now, I am not sure if this will work 100% since I can't test it right now but I am doing my best to help. After you disabled your touch events on your ScrollView now you need to hide the ScrollBar, because you can't use it since it is part of the HorizontalScrollView and it doesn't have only listeners of himself so I think each touch event will be intercepted by the HorizontalScrollView itself and not separated on content and scrollbar. To hide the scrollbar in your HorizontalScrollView you can use XML attribute like this:

    android:scrollbars="none"
    

    or from your code like this:

    view.setVerticalScrollBarEnabled(false);
    view.setHorizontalScrollBarEnabled(false);
    

    Now you need to create your own scrollbar or just buttons that will do the scrolling inside the view. These buttons will change the scrolling position onClick event. But first, let's disable the scrolling on HorizontalScrollView.

    class LockableScrollView extends HorizontalScrollView {
    
        ...
    
        // true if we can scroll (not locked)
        // false if we cannot scroll (locked)
        private boolean mScrollable = true;
    
        public void setScrollingEnabled(boolean enabled) {
            mScrollable = enabled;
        }
    
        public boolean isScrollable() {
            return mScrollable;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            // Do not allow touch events.
            return false;
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            // Do not allow touch events.
            return false;
        }
    
    }
    

    Now instead of classic HorizontalScrollView you can use this:

    <com.mypackagename.LockableScrollView 
        android:id="@+id/QuranGalleryScrollView" 
        android:layout_height="fill_parent" 
        android:layout_width="fill_parent">
    
        //your view
    
    </com.mypackagename.LockableScrollView>
    

    This is from the answer to this question: https://stackoverflow.com/a/5763815/14759470

    Now, let's go back to those buttons. Let's say you have two buttons on each side, one for scroll to right and one for scroll to left. You can handle scrolling like this:

    rightBtn.setOnClickListener(new View.OnClickListener() {
    
        @Override
        public void onClick(View v) {
            hsv.scrollTo((int)hsv.getScrollX() + 10, (int)hsv.getScrollY());
        }
    });
    

    or if you want it be smooth you can use onTouchListener like this:

    rightBtn.setOnTouchListener(new View.OnTouchListener() {
    
        private Handler mHandler;
        private long mInitialDelay = 300;
        private long mRepeatDelay = 100;
    
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    if (mHandler != null)
                        return true;
                    mHandler = new Handler();
                    mHandler.postDelayed(mAction, mInitialDelay);
                    break;
                case MotionEvent.ACTION_UP:
                    if (mHandler == null)
                        return true;
                    mHandler.removeCallbacks(mAction);
                    mHandler = null;
                    break;
            }
            return false;
        }
    
        Runnable mAction = new Runnable() {
            @Override
            public void run() {
                hsv.scrollTo((int) hsv.getScrollX() + 10, (int) hsv.getScrollY());
                mHandler.postDelayed(mAction, mRepeatDelay);
            }
        };
    });
    

    where hsv is your HorizontalScrollView. Now you can follow the logic for the left button. To handle show/hide buttons when the view is at the start/end you can follow this question and answers: https://stackoverflow.com/a/7672711/14759470

    If this isn't something you would like to do then I am not sure of other options you have since my knowledge can only go this far. But, maybe consider looking at some third-party libraries on GitHub or somewhere else which are working with audio files. Like this one: https://github.com/ferPrieto/SoundLine

    If you run into any problems while implementing this code please let me know in the comments. Since I didn't have time to test this before posting as an answer, but I'll be glad to help you with any future issues you come across.