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