From 380987785507a8580e65d5c4796a5b8ca62a57f0 Mon Sep 17 00:00:00 2001 From: "denis.druzhinin" Date: Mon, 17 Apr 2017 16:02:09 +0700 Subject: [PATCH] Add sample project code --- ListenScrollChanges/.gitignore | 9 + ListenScrollChanges/.idea/compiler.xml | 22 +++ .../.idea/copyright/profiles_settings.xml | 3 + ListenScrollChanges/.idea/encodings.xml | 6 + ListenScrollChanges/.idea/gradle.xml | 18 ++ ListenScrollChanges/.idea/misc.xml | 93 ++++++++++ ListenScrollChanges/.idea/modules.xml | 9 + .../.idea/runConfigurations.xml | 12 ++ ListenScrollChanges/app/.gitignore | 1 + ListenScrollChanges/app/build.gradle | 30 ++++ ListenScrollChanges/app/proguard-rules.pro | 25 +++ .../ExampleInstrumentedTest.java | 26 +++ .../app/src/main/AndroidManifest.xml | 23 +++ .../ListenScrollChangesHelper.java | 132 +++++++++++++++ .../OnScrollChangeListenerAdapter.java | 22 +++ .../OnScrollChangeListenerCompat.java | 10 ++ .../sample/MainActivity.java | 67 ++++++++ .../app/src/main/res/layout/activity_main.xml | 43 +++++ .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3418 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4208 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2206 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2555 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4842 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6114 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7718 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10056 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10486 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 14696 bytes .../app/src/main/res/values/colors.xml | 6 + .../app/src/main/res/values/strings.xml | 3 + .../app/src/main/res/values/styles.xml | 11 ++ .../listenscrollchanges/ExampleUnitTest.java | 17 ++ ListenScrollChanges/build.gradle | 23 +++ ListenScrollChanges/gradle.properties | 17 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + ListenScrollChanges/gradlew | 160 ++++++++++++++++++ ListenScrollChanges/gradlew.bat | 90 ++++++++++ ListenScrollChanges/settings.gradle | 1 + 39 files changed, 885 insertions(+) create mode 100644 ListenScrollChanges/.gitignore create mode 100644 ListenScrollChanges/.idea/compiler.xml create mode 100644 ListenScrollChanges/.idea/copyright/profiles_settings.xml create mode 100644 ListenScrollChanges/.idea/encodings.xml create mode 100644 ListenScrollChanges/.idea/gradle.xml create mode 100644 ListenScrollChanges/.idea/misc.xml create mode 100644 ListenScrollChanges/.idea/modules.xml create mode 100644 ListenScrollChanges/.idea/runConfigurations.xml create mode 100644 ListenScrollChanges/app/.gitignore create mode 100644 ListenScrollChanges/app/build.gradle create mode 100644 ListenScrollChanges/app/proguard-rules.pro create mode 100644 ListenScrollChanges/app/src/androidTest/java/com/bugsee/shared/listenscrollchanges/ExampleInstrumentedTest.java create mode 100644 ListenScrollChanges/app/src/main/AndroidManifest.xml create mode 100644 ListenScrollChanges/app/src/main/java/com/bugsee/shared/listenscrollchanges/ListenScrollChangesHelper.java create mode 100644 ListenScrollChanges/app/src/main/java/com/bugsee/shared/listenscrollchanges/OnScrollChangeListenerAdapter.java create mode 100644 ListenScrollChanges/app/src/main/java/com/bugsee/shared/listenscrollchanges/OnScrollChangeListenerCompat.java create mode 100644 ListenScrollChanges/app/src/main/java/com/bugsee/shared/listenscrollchanges/sample/MainActivity.java create mode 100644 ListenScrollChanges/app/src/main/res/layout/activity_main.xml create mode 100644 ListenScrollChanges/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 ListenScrollChanges/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 ListenScrollChanges/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 ListenScrollChanges/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 ListenScrollChanges/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 ListenScrollChanges/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 ListenScrollChanges/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 ListenScrollChanges/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 ListenScrollChanges/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 ListenScrollChanges/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 ListenScrollChanges/app/src/main/res/values/colors.xml create mode 100644 ListenScrollChanges/app/src/main/res/values/strings.xml create mode 100644 ListenScrollChanges/app/src/main/res/values/styles.xml create mode 100644 ListenScrollChanges/app/src/test/java/com/bugsee/shared/listenscrollchanges/ExampleUnitTest.java create mode 100644 ListenScrollChanges/build.gradle create mode 100644 ListenScrollChanges/gradle.properties create mode 100644 ListenScrollChanges/gradle/wrapper/gradle-wrapper.jar create mode 100644 ListenScrollChanges/gradle/wrapper/gradle-wrapper.properties create mode 100644 ListenScrollChanges/gradlew create mode 100644 ListenScrollChanges/gradlew.bat create mode 100644 ListenScrollChanges/settings.gradle diff --git a/ListenScrollChanges/.gitignore b/ListenScrollChanges/.gitignore new file mode 100644 index 0000000..39fb081 --- /dev/null +++ b/ListenScrollChanges/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/ListenScrollChanges/.idea/compiler.xml b/ListenScrollChanges/.idea/compiler.xml new file mode 100644 index 0000000..96cc43e --- /dev/null +++ b/ListenScrollChanges/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ListenScrollChanges/.idea/copyright/profiles_settings.xml b/ListenScrollChanges/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/ListenScrollChanges/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/ListenScrollChanges/.idea/encodings.xml b/ListenScrollChanges/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/ListenScrollChanges/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ListenScrollChanges/.idea/gradle.xml b/ListenScrollChanges/.idea/gradle.xml new file mode 100644 index 0000000..7ac24c7 --- /dev/null +++ b/ListenScrollChanges/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/ListenScrollChanges/.idea/misc.xml b/ListenScrollChanges/.idea/misc.xml new file mode 100644 index 0000000..53c2adc --- /dev/null +++ b/ListenScrollChanges/.idea/misc.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + Android Lint + + + Control flow issuesJava + + + Java + + + Naming conventionsJava + + + XML + + + + + Android + + + + + + + + + + + + + + + + + + + + + + + + + 1.7 + + + + + + + + \ No newline at end of file diff --git a/ListenScrollChanges/.idea/modules.xml b/ListenScrollChanges/.idea/modules.xml new file mode 100644 index 0000000..364cd4f --- /dev/null +++ b/ListenScrollChanges/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/ListenScrollChanges/.idea/runConfigurations.xml b/ListenScrollChanges/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/ListenScrollChanges/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/ListenScrollChanges/app/.gitignore b/ListenScrollChanges/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/ListenScrollChanges/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/ListenScrollChanges/app/build.gradle b/ListenScrollChanges/app/build.gradle new file mode 100644 index 0000000..7b805a6 --- /dev/null +++ b/ListenScrollChanges/app/build.gradle @@ -0,0 +1,30 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 25 + buildToolsVersion "25.0.2" + defaultConfig { + applicationId "com.example.my.test.listenscrollchanges" + minSdkVersion 14 + targetSdkVersion 25 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:25.3.0' + compile 'com.android.support.constraint:constraint-layout:1.0.0-beta1' + testCompile 'junit:junit:4.12' +} diff --git a/ListenScrollChanges/app/proguard-rules.pro b/ListenScrollChanges/app/proguard-rules.pro new file mode 100644 index 0000000..90179b6 --- /dev/null +++ b/ListenScrollChanges/app/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in E:\Android_sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/ListenScrollChanges/app/src/androidTest/java/com/bugsee/shared/listenscrollchanges/ExampleInstrumentedTest.java b/ListenScrollChanges/app/src/androidTest/java/com/bugsee/shared/listenscrollchanges/ExampleInstrumentedTest.java new file mode 100644 index 0000000..de4ab95 --- /dev/null +++ b/ListenScrollChanges/app/src/androidTest/java/com/bugsee/shared/listenscrollchanges/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.bugsee.shared.listenscrollchanges; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.example.my.test.listenscrollchanges", appContext.getPackageName()); + } +} diff --git a/ListenScrollChanges/app/src/main/AndroidManifest.xml b/ListenScrollChanges/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..fd2fbef --- /dev/null +++ b/ListenScrollChanges/app/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ListenScrollChanges/app/src/main/java/com/bugsee/shared/listenscrollchanges/ListenScrollChangesHelper.java b/ListenScrollChanges/app/src/main/java/com/bugsee/shared/listenscrollchanges/ListenScrollChangesHelper.java new file mode 100644 index 0000000..e863713 --- /dev/null +++ b/ListenScrollChanges/app/src/main/java/com/bugsee/shared/listenscrollchanges/ListenScrollChangesHelper.java @@ -0,0 +1,132 @@ +package com.bugsee.shared.listenscrollchanges; + +import android.annotation.SuppressLint; +import android.graphics.Point; +import android.os.Build; +import android.view.View; +import android.view.ViewTreeObserver; + +import java.util.Map; +import java.util.WeakHashMap; + +/** + * Created by denis.druzhinin, Bugsee Inc, https://www.bugsee.com + */ +public class ListenScrollChangesHelper { + private final WeakHashMap mViewToListenerMap = new WeakHashMap<>(); + + @SuppressLint("NewApi") + public void addViewToListen(View view, OnScrollChangeListenerCompat listener) { + if (view == null || listener == null) + return; + + // Fall-back to native solution on newer Android devices. + if (useNativeScrollChangeListener()) { + view.setOnScrollChangeListener(new OnScrollChangeListenerAdapter(listener)); + mViewToListenerMap.put(view, null); + return; + } + + if (!mViewToListenerMap.containsKey(view)) { + // Handle case, when previously added view has the same ViewTreeObserver. + view.getViewTreeObserver().removeOnScrollChangedListener(mObserverOnScrollChangedListener); + view.getViewTreeObserver().addOnScrollChangedListener(mObserverOnScrollChangedListener); + + view.removeOnLayoutChangeListener(mLayoutChangeListener); + view.addOnLayoutChangeListener(mLayoutChangeListener); + } + Item item = new Item(new Point(view.getScrollX(), view.getScrollY()), listener, view.getViewTreeObserver()); + mViewToListenerMap.put(view, item); + } + + @SuppressLint("NewApi") + public void removeViewToListen(View view) { + if (view == null || mViewToListenerMap.size() == 0) + return; + + view.removeOnLayoutChangeListener(mLayoutChangeListener); + if (useNativeScrollChangeListener()) { + view.setOnScrollChangeListener(null); + } else if (!haveAnotherViewWithSameObserver(view)) { + view.getViewTreeObserver().removeOnScrollChangedListener(mObserverOnScrollChangedListener); + } + mViewToListenerMap.remove(view); + } + + public void clear() { + for (View view : mViewToListenerMap.keySet()) { + removeViewToListen(view); + } + } + + private boolean haveAnotherViewWithSameObserver(View view) { + for (Map.Entry entry : mViewToListenerMap.entrySet()) { + if (entry.getKey() != view && entry.getKey().getViewTreeObserver() == view.getViewTreeObserver()) + return true; + } + return false; + } + + // If ViewTreeObserver is not alive, it will throw exception on call to any method except isAlive(). + private static void safeAddOnScrollChangeListener(ViewTreeObserver observer, ViewTreeObserver.OnScrollChangedListener listener) { + if (observer.isAlive()) { + observer.addOnScrollChangedListener(listener); + } + } + + private static void safeRemoveOnScrollChangeListener(ViewTreeObserver observer, ViewTreeObserver.OnScrollChangedListener listener) { + if (observer.isAlive()) { + observer.removeOnScrollChangedListener(listener); + } + } + + private static boolean useNativeScrollChangeListener() { + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M); + } + + private final ViewTreeObserver.OnScrollChangedListener mObserverOnScrollChangedListener = new ViewTreeObserver.OnScrollChangedListener() { + @Override + public void onScrollChanged() { + for (Map.Entry entry : mViewToListenerMap.entrySet()) { + int scrollX = Math.round(entry.getKey().getScrollX()); + int scrollY = Math.round(entry.getKey().getScrollY()); + int oldScrollX = entry.getValue().ScrollPosition.x; + int oldScrollY = entry.getValue().ScrollPosition.y; + if (scrollX != oldScrollX || scrollY != oldScrollY) { + entry.getValue().Listener.onScrollChange(entry.getKey(), scrollX, scrollY, oldScrollX, oldScrollY); + entry.getValue().ScrollPosition.x = scrollX; + entry.getValue().ScrollPosition.y = scrollY; + } + } + } + }; + + // ViewTreeObserver is not guaranteed to remain valid for the lifetime of view. + private final View.OnLayoutChangeListener mLayoutChangeListener = new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + Item item = mViewToListenerMap.get(view); + if (item == null) + return; + + if (item.Observer != view.getViewTreeObserver()) { + safeRemoveOnScrollChangeListener(item.Observer, mObserverOnScrollChangedListener); + + item.Observer = view.getViewTreeObserver(); + safeAddOnScrollChangeListener(item.Observer, mObserverOnScrollChangedListener); + } + } + }; + + private static class Item { + Point ScrollPosition; + OnScrollChangeListenerCompat Listener; + ViewTreeObserver Observer; + + public Item(Point scrollPosition, OnScrollChangeListenerCompat listener, ViewTreeObserver observer) { + ScrollPosition = scrollPosition; + Listener = listener; + Observer = observer; + } + } +} diff --git a/ListenScrollChanges/app/src/main/java/com/bugsee/shared/listenscrollchanges/OnScrollChangeListenerAdapter.java b/ListenScrollChanges/app/src/main/java/com/bugsee/shared/listenscrollchanges/OnScrollChangeListenerAdapter.java new file mode 100644 index 0000000..765d2d3 --- /dev/null +++ b/ListenScrollChanges/app/src/main/java/com/bugsee/shared/listenscrollchanges/OnScrollChangeListenerAdapter.java @@ -0,0 +1,22 @@ +package com.bugsee.shared.listenscrollchanges; + +import android.os.Build; +import android.support.annotation.RequiresApi; +import android.view.View; + +/** + * Created by denis.druzhinin, Bugsee Inc, https://www.bugsee.com + */ +@RequiresApi(api = Build.VERSION_CODES.M) +public class OnScrollChangeListenerAdapter implements View.OnScrollChangeListener { + private final OnScrollChangeListenerCompat mOnScrollChangeListener; + + public OnScrollChangeListenerAdapter(OnScrollChangeListenerCompat onScrollChangeListener) { + mOnScrollChangeListener = onScrollChangeListener; + } + + @Override + public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { + mOnScrollChangeListener.onScrollChange(v, scrollX, scrollY, oldScrollX, oldScrollY); + } +} diff --git a/ListenScrollChanges/app/src/main/java/com/bugsee/shared/listenscrollchanges/OnScrollChangeListenerCompat.java b/ListenScrollChanges/app/src/main/java/com/bugsee/shared/listenscrollchanges/OnScrollChangeListenerCompat.java new file mode 100644 index 0000000..864fabc --- /dev/null +++ b/ListenScrollChanges/app/src/main/java/com/bugsee/shared/listenscrollchanges/OnScrollChangeListenerCompat.java @@ -0,0 +1,10 @@ +package com.bugsee.shared.listenscrollchanges; + +import android.view.View; + +/** + * Created by denis.druzhinin, Bugsee Inc, https://www.bugsee.com + */ +public interface OnScrollChangeListenerCompat { + void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY); +} \ No newline at end of file diff --git a/ListenScrollChanges/app/src/main/java/com/bugsee/shared/listenscrollchanges/sample/MainActivity.java b/ListenScrollChanges/app/src/main/java/com/bugsee/shared/listenscrollchanges/sample/MainActivity.java new file mode 100644 index 0000000..6da0c03 --- /dev/null +++ b/ListenScrollChanges/app/src/main/java/com/bugsee/shared/listenscrollchanges/sample/MainActivity.java @@ -0,0 +1,67 @@ +package com.bugsee.shared.listenscrollchanges.sample; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.View; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.Button; + +import com.bugsee.shared.listenscrollchanges.ListenScrollChangesHelper; +import com.bugsee.shared.listenscrollchanges.OnScrollChangeListenerCompat; +import com.bugsee.shared.listenscrollchanges.R; + +import java.text.MessageFormat; + +/** + * Created by denis.druzhinin, Bugsee Inc, https://www.bugsee.com + */ +public class MainActivity extends AppCompatActivity { + private static final String TAG = MainActivity.class.getSimpleName(); + + private WebView mWebView; + private Button mListenButton; + private Button mStopListeningButton; + private final ListenScrollChangesHelper mScrollChangesHelper = new ListenScrollChangesHelper(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + mListenButton = (Button)findViewById(R.id.listenButton); + mListenButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listenScrollEvents(true); + } + }); + mStopListeningButton = (Button)findViewById(R.id.stopListeningButton); + mStopListeningButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listenScrollEvents(false); + } + }); + + mWebView = (WebView) findViewById(R.id.webview); + mWebView.setWebViewClient(new WebViewClient()); + mWebView.loadUrl("https://www.google.com"); + } + + private void listenScrollEvents(boolean listen) { + if (listen) { + mScrollChangesHelper.addViewToListen(mWebView, mScrollChangeListener); + } else { + mScrollChangesHelper.removeViewToListen(mWebView); + } + } + + private final OnScrollChangeListenerCompat mScrollChangeListener = new OnScrollChangeListenerCompat() { + @Override + public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { + Log.i(TAG, MessageFormat.format("Scroll event. X: {0}; Y: {1}; Old X: {2}; Old Y: {3}", scrollX, scrollY, oldScrollX, oldScrollY)); + } + }; +} diff --git a/ListenScrollChanges/app/src/main/res/layout/activity_main.xml b/ListenScrollChanges/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..1d2abe6 --- /dev/null +++ b/ListenScrollChanges/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,43 @@ + + + + + + + +