Magic changes ScrollView to become Android's automatic carousel artifact

Posted by ccbayer on Sat, 04 Jan 2020 05:14:31 +0100

One of the requirements of the APP in the previous work is to cycle and rotate a bunch of pictures and texts. So I used the ScrollView magic to change this requirement. The following is the operation screen of Demo. The TextView with the number from 0 to 100 is automatically rotated:

 

 

The specific implementation is as follows:

Vertical rotation ScrollView:

package ggg.project.dfff;

import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ScrollView;


/**
 * @author ChenJieZhu
 * Listen to ScrollView and scroll to the top or bottom to intercept related events
 */
public class SmartScrollView extends ScrollView {

    private boolean isScrolledToTop = true;     // Set the following values during initialization
    private boolean isScrolledToBottom = false;
    public SmartScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    private SmartScrollChangedListener mSmartScrollChangedListener;
    private Thread threadAutoRolling = null;
    private boolean threadAutoRollingStart = true, threadAutoRollingPause = false;
    private int autoRollingTimeMS = 0;
    /** Define listening interface */
    public interface SmartScrollChangedListener {
        void onScrolledToBottom();
        void onScrolledToTop();
    }

    public void setScanScrollChangedListener(SmartScrollChangedListener smartScrollChangedListener) {
        mSmartScrollChangedListener = smartScrollChangedListener;
    }

    @Override
    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
        super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        //If (Android. OS. Build. Version. Sdk_int < 9) {/ / API 9 and then use the onscrogled method to listen
            if (getScrollY() == 0) {    // Be careful to step on Pit 1: this cannot be getscrolly() < = 0
                isScrolledToTop = true;
                isScrolledToBottom = false;
            } else if (getScrollY() + getHeight() - getPaddingTop()-getPaddingBottom() == getChildAt(0).getHeight()) {
                // Step on the pit carefully 2: it can't be >=
                // Step on Pit 3 carefully (detail 2 that may be ignored): the easiest thing to ignore here is the padding on the top and bottom of ScrollView
                isScrolledToBottom = true;
                isScrolledToTop = false;
            } else {
                isScrolledToTop = false;
                isScrolledToBottom = false;
            }
            notifyScrollChangedListeners();
        //}
    }

    private void notifyScrollChangedListeners() {
        if (isScrolledToTop) {
            if (mSmartScrollChangedListener != null) {
                mSmartScrollChangedListener.onScrolledToTop();
            }
        } else if (isScrolledToBottom) {
            if (mSmartScrollChangedListener != null) {
                mSmartScrollChangedListener.onScrolledToBottom();
            }
        }
    }


    public boolean isScrolledToTop() {
        return isScrolledToTop;
    }

    public boolean isScrolledToBottom() {
        return isScrolledToBottom;
    }

    /**ScrollView Automatic rolling
     * @author Chen Jiezhu **/
    public void startAutoRolling(final Activity activity, final ViewGroup parentView, final int stepPixel, final int stepTimeSpiltMS){
        stopAutoRolling();
        //Set bottom and top touch events
        setScanScrollChangedListener(new SmartScrollView.SmartScrollChangedListener() {
            private boolean onScrolledToBottom = false;
            @Override
            public void onScrolledToBottom() {
                synchronized (SmartScrollView.this){
                    synchronized (parentView){
                        try{
                            //Even if the user turns on auto scrolling, scrolling is necessary only if the content is longer than ScrollView
                            if(parentView.getChildCount() <= 0) return;
                            if(parentView.getHeight() > SmartScrollView.this.getHeight() + 30) {
                                if(parentView.getChildCount() > 1) {
                                    onScrolledToBottom = true;
                                    View view = parentView.getChildAt(0);
                                    parentView.removeViewAt(0);
                                    parentView.addView(view);
                                    //Returns the height of the pixel before the control added to the tail of the container in case of an unsightly jerk and jam
                                    scrollBy(0, -view.getMeasuredHeight());
                                }else{
                                    View view = parentView.getChildAt(0);
                                    scrollBy(0, -view.getMeasuredHeight());
                                }
                            }
                            onScrolledToBottom = false;
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                }
            }

            @Override
            public void onScrolledToTop() {
                synchronized (parentView){
                    try{
                        if(parentView.getChildCount() <=0) return;
                        if(parentView.getHeight() > SmartScrollView.this.getHeight() + 30) {
                            if(parentView.getChildCount() > 1){
                                //On September 4, 2017, spillover was found
                                if(onScrolledToBottom) return;
                                View view = parentView.getChildAt(parentView.getChildCount() - 1);
                                parentView.removeViewAt(parentView.getChildCount() - 1);
                                if (view != null) parentView.addView(view, 0);
                                //Returns the height of the pixel before the control added to the top of the container to prevent unsightly jerks and jams
                                scrollBy(0, view.getMeasuredHeight());
                            }else{
//                            View view = parentView.getChildAt(parentView.getChildCount() - 1);
//                            scrollBy(0, view.getMeasuredHeight());
                            }
                        }
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        });
        //Start refresh thread
        threadAutoRollingStart = true;
        threadAutoRolling = new Thread(new Runnable() {
            private Runnable threadRolling = new Runnable() {
                @Override
                public void run() {
                    //Even if the user turns on auto scrolling, the content must be longer than ScrollView, so it is necessary to scroll without any idle work
                    if(parentView.getHeight() > SmartScrollView.this.getHeight()){
                        scrollBy(0, stepPixel);
                    }
                }
            };

            @Override
            public void run() {
                while(threadAutoRollingStart && !activity.isFinishing()){
                    try {
                        //Pause state switch state machine
                        if(threadAutoRollingPause){
                            Thread.sleep(SmartScrollView.this.autoRollingTimeMS);
                            //State machine state reversal
                            threadAutoRollingPause = !threadAutoRollingPause;
                        }
                        activity.runOnUiThread(threadRolling);
                        Thread.sleep(stepTimeSpiltMS);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        threadAutoRolling.start();
    }

    /**Damping: 1000 is to reduce the inertia rolling speed by 1000 times, approximately drag operation. **/
    @Override
    public void fling(int velocity) {
        super.fling(velocity / 1000);
    }

    /**ScrollView Auto scroll stop
     * Remember to call this method when you are finished using the control
     * Turn off auto scroll threads
     * Otherwise, thread residue will be generated
     * @author Chen Jiezhu **/
    public void stopAutoRolling(){
        if(threadAutoRolling != null){
            threadAutoRollingStart = false;
            try{
                threadAutoRolling.interrupt();
            }catch (Exception e){

            }
        }
    }

    /**ScrollView Auto roll pause
     * @author Chen Jiezhu **/
    public void pauseAutoRolling(int pauseTimeMS){
        autoRollingTimeMS = pauseTimeMS;
        threadAutoRollingPause = true;
    }

    @Override
    protected void onDetachedFromWindow() {
        stopAutoRolling();
        super.onDetachedFromWindow();
    }

    @Override
    public void removeAllViews() {
        stopAutoRolling();
        super.removeAllViews();
    }

    @Override
    public void removeAllViewsInLayout() {
        stopAutoRolling();
        super.removeAllViewsInLayout();
    }

    @Override
    protected void removeDetachedView(View child, boolean animate) {
        super.removeDetachedView(child, animate);
    }

    @Override
    protected void detachViewFromParent(View child) {
        stopAutoRolling();
        super.detachViewFromParent(child);
    }

    @Override
    protected void detachViewFromParent(int index) {
        stopAutoRolling();
        super.detachViewFromParent(index);
    }

    @Override
    protected void detachViewsFromParent(int start, int count) {
        stopAutoRolling();
        super.detachViewsFromParent(start, count);
    }

    @Override
    protected void detachAllViewsFromParent() {
        stopAutoRolling();
        super.detachAllViewsFromParent();
    }
}

 

Horizontal rotation ScrollView:

package ggg.project.dfff;

import android.app.Activity;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
/**
 * Created by cjz on 2017/9/6.
 */

public class SmartScrollViewHorizontalStyle2 extends HorizontalScrollView{

    private boolean isScrolledToTop = true;     // Set the following values during initialization
    private boolean isScrolledToBottom = false;
    public SmartScrollViewHorizontalStyle2(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    private SmartScrollChangedListener mSmartScrollChangedListener;
    private Thread threadAutoRolling = null;
    private boolean threadAutoRollingStart = true;
    private boolean threadAutoRollingPause = false;
    private boolean isBackMoving = false;
    private int autoRollingTimeMS = 0;
    private class Constant {
        public static final int BACK_MOVING = 0;
    }
    //Directly rewrite anonymously (no inheritance because there is no added method) and generate the object
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case Constant.BACK_MOVING:
                    scrollBy(-msg.arg2 , 0);
                    break;
            }
            super.handleMessage(msg);
        }
    };


    /** Define listening interface */
    public interface SmartScrollChangedListener {
        void onScrolledToBottom();
        void onScrolledToTop();
    }

    public void setScanScrollChangedListener(SmartScrollChangedListener smartScrollChangedListener) {
        mSmartScrollChangedListener = smartScrollChangedListener;
    }

    @Override
    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
        super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
//        if (scrollY == 0) {
//            isScrolledToTop = clampedY;
//            isScrolledToBottom = false;
//        } else {
//            isScrolledToTop = false;
//            isScrolledToBottom = clampedY;
//        }
//        notifyScrollChangedListeners();
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        //If (Android. OS. Build. Version. Sdk_int < 9) {/ / API 9 and then use the onscrogled method to listen
        if (getScrollX() == 0) {    // Be careful to step on Pit 1: this cannot be getscrolly() < = 0
            isScrolledToTop = true;
            isScrolledToBottom = false;
            //} else if (getScrollY() + getHeight() - getPaddingTop()-getPaddingBottom() == getChildAt(0).getHeight()) {
        } else if (getScrollX() + getWidth() - getPaddingLeft()-getPaddingRight() == getChildAt(0).getWidth()) {
            // Step on the pit carefully 2: it can't be >=
            // Step on Pit 3 carefully (detail 2 that may be ignored): the easiest thing to ignore here is the padding on the top and bottom of ScrollView
            isScrolledToBottom = true;
            isScrolledToTop = false;
        } else {
            isScrolledToTop = false;
            isScrolledToBottom = false;
        }
        notifyScrollChangedListeners();
        //}
        // Sometimes I am used to writing code. In order to be compatible with some strange border situations, the above code will be written as the case of < =, > = and the result will be a bug
        // When I wrote this, I wrote as follows: getscrolly() + getheight() > = getchildat (0). Getheight()
        // It turns out that it's fast to slide to the bottom, but it hasn't arrived yet. It will be found that the above conditions are true, leading to wrong judgment
        // Reason: getScrollY() value is not absolutely reliable, it will exceed the boundary value, but it will recover correctly, resulting in the above calculation conditions are not tenable
        // It makes sense to think about it carefully. When the ScrollView of the system dynamically calculates the scrollY when it processes scrolling, it will also have the situation of over boundary re correction
    }

    private void notifyScrollChangedListeners() {
        if (isScrolledToTop) {
            if (mSmartScrollChangedListener != null) {
                mSmartScrollChangedListener.onScrolledToTop();
            }
        } else if (isScrolledToBottom) {
            if (mSmartScrollChangedListener != null) {
                mSmartScrollChangedListener.onScrolledToBottom();
            }
        }
    }


    public boolean isScrolledToTop() {
        return isScrolledToTop;
    }

    public boolean isScrolledToBottom() {
        return isScrolledToBottom;
    }
    public void startAutoRolling(final Activity activity, final ViewGroup parentView, final int stepPixel, final int stepTimeSpiltMS){
        stopAutoRolling();
        //Set bottom and top touch events
        /**ScrollView Automatic rolling
         * @author Chen Jiezhu **/

        setScanScrollChangedListener(new SmartScrollViewHorizontalStyle2.SmartScrollChangedListener() {
            private boolean onScrolledToBottom = false;

            @Override
            public void onScrolledToBottom() {
                synchronized (parentView){
                    //Even if the user turns on auto scrolling, scrolling is necessary only if the content is longer than ScrollView
                    if(parentView.getChildCount() <= 0) return;
                    if(parentView.getWidth() > SmartScrollViewHorizontalStyle2.this.getWidth() + 5) {
                        //When there are only multiple controls
                        if(parentView.getChildCount() > 1) {
                            onScrolledToBottom = true;
                            ImageView imageView = null;
                            if(!(((String)parentView.getChildAt(0).getTag() != null ? (String)parentView.getChildAt(0).getTag() : "")).equals("isFakeItem")){
                                View view = parentView.getChildAt(0);
                                imageView = new ImageView(activity);
                                imageView.setLayoutParams(view.getLayoutParams());
                                imageView.setImageBitmap(view.getDrawingCache());
                                imageView.setTag("isFakeItem");
                                parentView.removeViewAt(0);
                                parentView.addView(imageView, 0);

                                parentView.addView(view);
                                //Returns the height of the pixel before the control added to the tail of the container in case of an unsightly jerk and jam
                                scrollBy(-view.getMeasuredWidth(), 0);
                                Log.i("ssvh", "Added fakeItem");
                            }
                            else{
                                //Make sure fakeItem is rolled out before clearing
                                parentView.removeViewAt(0);
//                                scrollBy(imageView != null ? imageView.getMeasuredWidth() : 0, 0);
                                Log.i("ssvh","Eliminated fakeItem");
                            }
                        //When there is only one control, dragging during TODO roll may cause jitter
                        }else{
                            //Method 1: roll back in an instant
//                            View view = parentView.getChildAt(0);
//                            scrollBy(-view.getMeasuredWidth(), 0);
                            //Method 2: slow rollback
                            if(!isBackMoving){
                                isBackMoving = true;
                                new Thread(new Runnable() {
                                    @Override
                                    public void run() {
                                        View view = parentView.getChildAt(0);
                                        //The pause time is a little too long. Resolved: because the displayed pixel width is not subtracted
                                        pauseAutoRolling(stepTimeSpiltMS * ((view.getMeasuredWidth() - SmartScrollViewHorizontalStyle2.this.getWidth()) / stepPixel) + 1000);
                                        int backStep =  ((view.getMeasuredWidth() - SmartScrollViewHorizontalStyle2.this.getWidth()) / stepPixel);
                                        while(backStep-- > 0 && isBackMoving){
                                            Message msg = new Message();
                                            msg.what = Constant.BACK_MOVING;
                                            msg.arg2 = stepPixel;
                                            handler.sendMessage(msg);
                                            try {
                                                Thread.sleep(stepTimeSpiltMS);
                                            } catch (InterruptedException e) {
                                                e.printStackTrace();
                                            }
                                        }
                                        isBackMoving = false;
                                    }
                                }).start();
                            }
                        }
                        onScrolledToBottom = false;
                    }
                }
            }

            @Override
            public void onScrolledToTop() {
                synchronized (parentView){
                    if(parentView.getChildCount() <=0) return;
                    if(parentView.getWidth() > SmartScrollViewHorizontalStyle2.this.getWidth() + 30) {
                        if(parentView.getChildCount() > 1){
                            if(onScrolledToBottom) return;
                            View view = parentView.getChildAt(parentView.getChildCount() - 1);
                            parentView.removeViewAt(parentView.getChildCount() - 1);
                            if (view != null) parentView.addView(view, 0);
                            //Returns the height of the pixel before the control added to the top of the container to prevent unsightly jerks and jams
                            scrollBy(view.getMeasuredWidth(), 0);
                        }
                    }
                }
            }
        });
        //Start refresh thread
        threadAutoRollingStart = true;
        threadAutoRolling = new Thread(new Runnable() {
            @Override
            public void run() {
                while(threadAutoRollingStart && !activity.isFinishing()){
                    try {
                        //Pause state switch state machine
                        if(threadAutoRollingPause){
                            Thread.sleep(SmartScrollViewHorizontalStyle2.this.autoRollingTimeMS);
                            //State machine state reversal
                            threadAutoRollingPause = !threadAutoRollingPause;
                        }
                        activity.runOnUiThread(runnableRolling);
                        Thread.sleep(stepTimeSpiltMS);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

            private Runnable runnableRolling = new Runnable() {
                @Override
                public void run() {
                    //Even if the user turns on auto scrolling, the content must be longer than ScrollView, so it is necessary to scroll without any idle work
                    if(parentView.getWidth() > SmartScrollViewHorizontalStyle2.this.getWidth()){
                        scrollBy(stepPixel, 0);
                    }
                }
            };
        });
        threadAutoRolling.start();
    }

    /**ScrollView Auto scroll stop
     * Remember to call this method when you are finished using the control
     * Turn off auto scroll threads
     * Otherwise, thread residue will be generated
     * @author Chen Jiezhu **/
    public void stopAutoRolling(){
        if(threadAutoRolling != null){
            threadAutoRollingStart = false;
            try{
                threadAutoRolling.interrupt();
            }catch (Exception e){

            }
        }
    }

    /**Damping: 1000 is to reduce the inertia rolling speed by 1000 times, approximately drag operation. **/
    @Override
    public void fling(int velocity) {
        super.fling(velocity / 1000);
    }

    /**ScrollView Auto roll pause
     * @author Chen Jiezhu **/
    public void pauseAutoRolling(int pauseTimeMS){
        autoRollingTimeMS = pauseTimeMS;
        threadAutoRollingPause = true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        isBackMoving = false;
        pauseAutoRolling(0);
        threadAutoRollingPause = false;
        return super.onTouchEvent(ev);
    }

    @Override
    protected void onDetachedFromWindow() {
        stopAutoRolling();
        super.onDetachedFromWindow();
    }

    @Override
    public void removeAllViews() {
        stopAutoRolling();
        super.removeAllViews();
    }

    @Override
    public void removeAllViewsInLayout() {
        stopAutoRolling();
        super.removeAllViewsInLayout();
    }

    @Override
    protected void removeDetachedView(View child, boolean animate) {
        super.removeDetachedView(child, animate);
    }

    @Override
    protected void detachViewFromParent(View child) {
        stopAutoRolling();
        super.detachViewFromParent(child);
    }

    @Override
    protected void detachViewFromParent(int index) {
        stopAutoRolling();
        super.detachViewFromParent(index);
    }

    @Override
    protected void detachViewsFromParent(int start, int count) {
        stopAutoRolling();
        super.detachViewsFromParent(start, count);
    }

    @Override
    protected void detachAllViewsFromParent() {
        stopAutoRolling();
        super.detachAllViewsFromParent();
    }
}

 

The principle is to make the top View move to the bottom when touching the bottom and the bottom View move to the top when touching the top by rewriting the event callback of touching the bottom and the top, so that the ScrollView can always cycle when rolling, and then make some fine adjustments to make the View add to the bottom or the top and then return to the position before adding back, so as to make it look more natural, so this is achieved Demand.

 

Call example:

Layout file:

 

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="ggg.project.dfff.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <ggg.project.dfff.SmartScrollView
            android:id="@+id/ssv_1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:scrollbars="none">

            <LinearLayout
                android:id="@+id/ll_content"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">

            </LinearLayout>
        </ggg.project.dfff.SmartScrollView>

        <ggg.project.dfff.SmartScrollViewHorizontalStyle2
            android:id="@+id/ssv_2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:scrollbars="none">

            <LinearLayout
                android:id="@+id/ll_content_2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">

            </LinearLayout>

        </ggg.project.dfff.SmartScrollViewHorizontalStyle2>
    </LinearLayout>

</FrameLayout>

Activity:

package ggg.project.dfff

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.view.MotionEvent
import android.view.View
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView

class MainActivity : AppCompatActivity() {

    var ssv1 : SmartScrollView ?= null
    var ssv2 : SmartScrollViewHorizontalStyle2 ?= null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        ssv1 = findViewById(R.id.ssv_1) as SmartScrollView  //Declare vertical scroll control
        ssv2 = findViewById(R.id.ssv_2) as SmartScrollViewHorizontalStyle2  //Declare horizontal scroll control
        var ll_content = findViewById(R.id.ll_content) as LinearLayout  //Vertical linear layout enclosed by scroll control
        var ll_content_2 = findViewById(R.id.ll_content_2) as LinearLayout  //Container sealed by scroll control
        //Add 100 textviews to the container for testing
        var textSize = 40f
        for(i in 0..100) {
            var textView = TextView(this)
            var textView2 = TextView(this)
            textView.setTextSize(textSize)
            textView2.setTextSize(textSize)
            textView.setText(i.toString())
            textView2.setText(i.toString() + "  ")
            ll_content.addView(textView)
            ll_content_2.addView(textView2)
        }
        //10 pixels per step, once every 10 milliseconds
        ssv1!!.startAutoRolling(this, ll_content, 10, 10)
        //10 pixels per step, every 100 milliseconds
        ssv2!!.startAutoRolling(this, ll_content_2, 10, 100)
        //Pause scrolling 2000 ms on tap
        ssv1!!.setOnTouchListener(View.OnTouchListener { v, event ->
            ssv1!!.pauseAutoRolling(2000)
            false
        })
    }

    override fun onDestroy() {
        ssv1!!.stopAutoRolling()
        super.onDestroy()
    }


}

We can focus on:

/ / 10 pixels per step, every 10 milliseconds
        ssv1!!.startAutoRolling(this, ll_content, 10, 10)
/ / 10 pixels per step, every 100 milliseconds
        ssv2!!.startAutoRolling(this, ll_content_2, 10, 100)

 

/ / pause scrolling for 2000 milliseconds when clicking
        ssv1!!.setOnTouchListener(View.OnTouchListener { v, event ->
            ssv1!!.pauseAutoRolling(2000)
            false
        })

This custom control allows the user to select the pixel step size of each rotation and how often to step, and allows the user to pause. There is also an OnTouch event that allows the user to implement a click event through a callback, such as suspending the rotation for a period of time after clicking.

Finally, I present an example project Demo address:

https://download.csdn.net/download/cjzjolly/10615127

 

Topics: Android xml encoding