117 |
118 |
119 |
--------------------------------------------------------------------------------
/app/src/main/java/com/stamenkovski/recyclerindicators/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.stamenkovski.recyclerindicators
2 |
3 | import android.content.Intent
4 | import androidx.appcompat.app.AppCompatActivity
5 | import android.os.Bundle
6 | import androidx.core.content.ContextCompat
7 | import android.view.View
8 | import com.arindicatorview.ARIndicatorView
9 | import com.stamenkovski.recyclerindicators.ViewPager.ViewPagerActivity
10 | import kotlinx.android.synthetic.main.activity_main.*
11 | import android.view.animation.Animation
12 | import android.view.animation.RotateAnimation
13 | import android.widget.Button
14 | import androidx.recyclerview.widget.RecyclerView
15 |
16 |
17 | class MainActivity : AppCompatActivity() {
18 |
19 | lateinit var recyclerView: RecyclerView
20 | lateinit var arIndicatorView: ARIndicatorView
21 |
22 | var didChangeIndicatorShape: Boolean = false
23 |
24 | private lateinit var buttonScrubbing: Button
25 |
26 | override fun onCreate(savedInstanceState: Bundle?) {
27 | super.onCreate(savedInstanceState)
28 | setContentView(R.layout.activity_main)
29 | recyclerView = recycler
30 | arIndicatorView = ar_indicator
31 | buttonScrubbing = button
32 |
33 | this.supportActionBar?.apply {
34 | title = "RecyclerView"
35 | }
36 |
37 | recyclerView.adapter = Adapter(this)
38 | recyclerView.layoutManager =
39 | androidx.recyclerview.widget.LinearLayoutManager(
40 | this,
41 | androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL,
42 | false
43 | )
44 | arIndicatorView.attachTo(recyclerView, true)
45 |
46 | }
47 |
48 | fun startViewPager(view: View) {
49 | startActivity(Intent(this, ViewPagerActivity::class.java))
50 | }
51 |
52 | fun changeIndicatorShape(view: View) {
53 |
54 | val an: RotateAnimation = if (!didChangeIndicatorShape) {
55 | RotateAnimation(
56 | 0.0f, 180.0f, (arIndicatorView.width / 2).toFloat(),
57 | (arIndicatorView.height / 2).toFloat()
58 | )
59 | } else {
60 | RotateAnimation(
61 | 180.0f, 0f, (arIndicatorView.width / 2).toFloat(),
62 | (arIndicatorView.height / 2).toFloat()
63 | )
64 | }
65 | this.didChangeIndicatorShape = !this.didChangeIndicatorShape
66 |
67 | an.duration = 1000
68 | an.repeatCount = 0
69 | an.repeatMode = Animation.REVERSE
70 | an.fillAfter = false
71 | an.isFillEnabled = true
72 |
73 | an.setAnimationListener(object : Animation.AnimationListener {
74 | override fun onAnimationRepeat(animation: Animation?) {
75 |
76 | }
77 |
78 | override fun onAnimationEnd(animation: Animation?) = if (didChangeIndicatorShape) {
79 | arIndicatorView.rotation = 0f
80 | arIndicatorView.indicatorSize = 50
81 | arIndicatorView.indicatorShape = R.drawable.my_shape
82 | arIndicatorView.selectionColor = ContextCompat.getColor(this@MainActivity, R.color.colorPrimary)
83 | arIndicatorView.indicatorColor = ContextCompat.getColor(this@MainActivity, R.color.colorAccent)
84 | arIndicatorView.isScrubbingEnabled = true
85 | arIndicatorView.isShouldAnimateOnScrubbing = true
86 | arIndicatorView.indicatorAnimation = R.anim.fade_in
87 | } else {
88 |
89 | arIndicatorView.indicatorSize = 20
90 | arIndicatorView.indicatorShape = 0
91 | arIndicatorView.selectionColor = ContextCompat.getColor(this@MainActivity, R.color.colorAccent)
92 | arIndicatorView.indicatorColor = ContextCompat.getColor(this@MainActivity, R.color.colorPrimary)
93 | arIndicatorView.selectedPosition = 5
94 | arIndicatorView.isScrubbingEnabled = false
95 | }
96 |
97 | override fun onAnimationStart(animation: Animation?) {
98 | }
99 |
100 | })
101 | arIndicatorView.startAnimation(an)
102 |
103 | }
104 |
105 | fun isScrubbingOn(button: View) {
106 | this.arIndicatorView.isScrubbingEnabled = !this.arIndicatorView.isScrubbingEnabled
107 | buttonScrubbing.text = "Scrubbing ${this.arIndicatorView.isScrubbingEnabled}"
108 | }
109 |
110 | fun animateScrubbing(view: View) {
111 | this.arIndicatorView.isShouldAnimateOnScrubbing = !this.arIndicatorView.isShouldAnimateOnScrubbing
112 | /* if (!this.arIndicatorView.isShouldAnimateOnScrubbing) {
113 | buttonAnimation.text = "Animation off"
114 | } else {
115 | buttonAnimation.text = "Animation on"
116 | }*/
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/arindicatorview/src/main/java/com/arindicatorview/IndicatorView.java:
--------------------------------------------------------------------------------
1 | package com.arindicatorview;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Color;
6 | import androidx.annotation.Nullable;
7 | import androidx.core.content.ContextCompat;
8 | import android.util.AttributeSet;
9 | import android.widget.ImageView;
10 | import android.widget.LinearLayout;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | public class IndicatorView extends LinearLayout {
16 |
17 | protected int indicatorColor;
18 | protected int selectionColor;
19 | protected int numberOfIndicators;
20 | protected int indicatorSize;
21 | protected int indicatorAnimation;
22 | protected int indicatorShape;
23 |
24 | protected boolean shouldAnimateOnScrubbing;
25 | protected boolean isScrubbingEnabled;
26 |
27 |
28 | protected List indicators = new ArrayList<>();
29 |
30 | public IndicatorView(Context context) {
31 | super(context);
32 | init(null, 0);
33 |
34 | }
35 |
36 | public IndicatorView(Context context, @Nullable AttributeSet attrs) {
37 | super(context, attrs);
38 | init(attrs, 0);
39 |
40 | }
41 |
42 | public IndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
43 | super(context, attrs, defStyleAttr);
44 | init(attrs, defStyleAttr);
45 |
46 | }
47 |
48 | private void init(AttributeSet attrs, int defStyle) {
49 | // Load attributes
50 | TypedArray styledAttributes = getContext().getTheme().obtainStyledAttributes(
51 | attrs, R.styleable.ARIndicatorView, defStyle, 0);
52 |
53 | this.indicatorColor = styledAttributes.getColor(R.styleable.ARIndicatorView_indicator_color, Color.LTGRAY);
54 | this.selectionColor = styledAttributes.getColor(R.styleable.ARIndicatorView_selected_color, Color.BLACK);
55 | this.numberOfIndicators = styledAttributes.getInteger(R.styleable.ARIndicatorView_number_of_indicators, 0);
56 | this.indicatorSize = styledAttributes.getInteger(R.styleable.ARIndicatorView_indicator_size, 10);
57 | this.indicatorAnimation = styledAttributes.getResourceId(R.styleable.ARIndicatorView_indicator_animation, 0);
58 | this.indicatorShape = styledAttributes.getResourceId(R.styleable.ARIndicatorView_indicator_shape, R.drawable.circle);
59 | this.isScrubbingEnabled = styledAttributes.getBoolean(R.styleable.ARIndicatorView_indicator_scrubbing, false);
60 | this.shouldAnimateOnScrubbing = styledAttributes.getBoolean(R.styleable.ARIndicatorView_animate_indicator_scrubbing, false);
61 |
62 | styledAttributes.recycle();
63 |
64 | if (isInEditMode()) {
65 | for (int i = 0; i < numberOfIndicators; i++) {
66 | drawCircle(i);
67 | }
68 | }
69 |
70 | }
71 |
72 | protected void drawCircle(int position) {
73 |
74 | ImageView imageView = new ImageView(getContext());
75 | imageView.setBackground(ContextCompat.getDrawable(getContext(), R.drawable.circle));
76 |
77 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
78 | LayoutParams.WRAP_CONTENT,
79 | LayoutParams.WRAP_CONTENT
80 | );
81 | if (indicatorShape == 0) {
82 | imageView.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.circle));
83 | } else {
84 | imageView.setImageDrawable(ContextCompat.getDrawable(getContext(), indicatorShape));
85 | }
86 | this.setupColors(imageView, position);
87 |
88 | layoutParams.setMargins(10, 10, 10, 10);
89 | layoutParams.width = indicatorSize;
90 | layoutParams.height = indicatorSize;
91 | addView(imageView, layoutParams);
92 | indicators.add(imageView);
93 | }
94 |
95 |
96 | private void setupColors(ImageView imageView, int position) {
97 | if (position == 0) {
98 | this.setActiveColorTo(imageView);
99 | } else {
100 | this.setUnActiveColorTo(imageView);
101 | }
102 | }
103 |
104 |
105 | protected void setActiveColorTo(ImageView imageView) {
106 | imageView.setColorFilter(selectionColor);
107 | imageView.requestLayout();
108 |
109 | }
110 |
111 | protected void setUnActiveColorTo(ImageView imageView) {
112 | imageView.setColorFilter(indicatorColor);
113 | imageView.invalidate();
114 | }
115 |
116 | public boolean isShouldAnimateOnScrubbing() {
117 | return shouldAnimateOnScrubbing;
118 | }
119 |
120 | public void setShouldAnimateOnScrubbing(boolean shouldAnimateOnScrubbing) {
121 | this.shouldAnimateOnScrubbing = shouldAnimateOnScrubbing;
122 | }
123 |
124 | public boolean isScrubbingEnabled() {
125 | return isScrubbingEnabled;
126 | }
127 |
128 | public void setScrubbingEnabled(boolean scrubbingEnabled) {
129 | isScrubbingEnabled = scrubbingEnabled;
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ARIndicatorView
2 |
3 | ARIndicatorView is Android library for showing indicators in RecyclerView and ViewPager.
4 |
5 | [](https://developer.android.com/guide/)
6 | [](https://twitter.com/intent/tweet?url=https://github.com/MartinStamenkovski/ARIndicatorView&text=ARIndicatorView%20Android&hashtags=Android,Indicators,RecyclerView)
7 |
8 | *Leave a star if you find this project useful.*
9 | ## Installation
10 | [](https://jitpack.io/#MartinStamenkovski/ARIndicatorView)
11 |
12 |
13 | 1. Add JitPack to your project build.gradle
14 | ```gradle
15 | allprojects {
16 | repositories {
17 | ...
18 | maven { url 'https://jitpack.io' }
19 | }
20 | }
21 | ```
22 | 2. Add the dependency in the application build.gradle
23 |
24 | ```gradle
25 | dependencies {
26 | implementation 'com.github.martinstamenkovski:ARIndicatorView:2.0.0'
27 | }
28 | ```
29 |
30 | ## Usage
31 | **XML**
32 | ```xml
33 |
48 | ```
49 |
50 | ## Kotlin or Java
51 |
52 | **You need to attach the ARIndicatorView to RecyclerView or ViewPager after populating the adapter, else the indicators will not create.**
53 |
54 | **Example**
55 | ```java
56 | recyclerView.adapter = Adapter(this, data)
57 | recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
58 | arIndicatorView.attachTo(recyclerView, true)
59 | ```
60 | - The parameter **_true_** indicates that RecyclerView will be paging, by default this is false.
61 |
62 | If you don't want to attach it to RecyclerView or ViewPager you can use the method **setNumberOfIndicators(numberOfIndicators)** later in code.
63 |
64 | ```java
65 | arIndicatorView.numberOfIndicators = 5
66 | ```
67 |
68 | ## Customization and method usage
69 | To start with **_number_of_indicators_**, in xml this method will only have effect while you are in preview to design the indicators to your liking, you can use it in your code for the indicators to appear.
70 | - Indicators width and height, default is 10
71 | ```java
72 | app:indicator_size="15"
73 | ```
74 | - Color to use when indicator is selected, default is BLACK.
75 | ```java
76 | app:selected_color="@color/colorPrimary"
77 | ```
78 | - Color to use when indicator is not selected, default is LTGRAY.
79 | ```java
80 | app:indicator_color="@color/colorAccent"
81 | ```
82 | - Indicator animation when is selected, by default no animation is provided.
83 | ```java
84 | app:indicator_animation="@anim/zoom_in"
85 | ```
86 | - Indicator shape you can use your own custom shape, or use the default one. Default is circle.
87 |
88 | ```java
89 | app:indicator_shape="@drawable/circle"
90 | ```
91 | #### Example with different shape
92 | *Note: this will not be the animation when you are changing from one shape to another.*
93 |
94 |
95 |
96 |
97 | - You can also use scrubbing on the indicators for faster scrolling through pages, default value is false.
98 |
99 | ```java
100 | app:indicator_scrubbing="true"
101 | ```
102 | When **indicator_scrubbing** is set to true you can also specify should the indicator animate when scrubbing.
103 | ```java
104 | app:animate_indicator_scrubbing="true" //Default value is false
105 | ```
106 | #### Example when scrubbing animation is on
107 |
108 |
109 |
110 |
111 | - Default orientation of the indicators is horizontal, but they can be placed vertical too.
112 | ```xml
113 | android:orientation="vertical"
114 | ```
115 | #### You can use all these methods in code too.
116 |
117 | ```java
118 | arIndicatorView.indicatorSize = 50
119 | arIndicatorView.indicatorShape = R.drawable.my_shape
120 | arIndicatorView.selectionColor = ContextCompat.getColor(this@MainActivity, R.color.colorPrimary)
121 | arIndicatorView.indicatorColor = ContextCompat.getColor(this@MainActivity, R.color.colorAccent)
122 | arIndicatorView.isScrubbingEnabled = true
123 | arIndicatorView.isShouldAnimateOnScrubbing = true
124 | arIndicatorView.indicatorAnimation = R.anim.fade_in
125 | ```
126 | ++ some extra methods:
127 | ```java
128 | setSelectedPosition(int position) //Selects the indicator at the given position
129 | removeIndicators() //Removes all indicators
130 | ```
131 |
132 | ### Todos
133 | - [ ] scrollToNext() -> Method to scroll to next item
134 | - [ ] scrollToPrevios() -> Method to scroll to previous item
135 | - [ ] Support custom shape from custom view
136 |
137 | ## Contributing
138 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
139 |
140 |
141 | ## License
142 | [MIT](https://choosealicense.com/licenses/mit/)
143 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/arindicatorview/src/main/java/com/arindicatorview/ARIndicatorView.java:
--------------------------------------------------------------------------------
1 | package com.arindicatorview;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.viewpager.widget.PagerAdapter;
6 | import androidx.viewpager.widget.ViewPager;
7 | import androidx.recyclerview.widget.LinearLayoutManager;
8 | import androidx.recyclerview.widget.PagerSnapHelper;
9 | import androidx.recyclerview.widget.RecyclerView;
10 | import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
11 | import android.util.AttributeSet;
12 | import android.view.MotionEvent;
13 | import android.view.animation.AnimationUtils;
14 | import android.widget.*;
15 |
16 |
17 | public class ARIndicatorView extends IndicatorView {
18 |
19 |
20 | private RecyclerView recyclerView;
21 | private ViewPager viewPager;
22 |
23 | private int selectedPosition = 0;
24 |
25 | private boolean isScrubbing = false;
26 |
27 | public ARIndicatorView(Context context) {
28 | super(context);
29 | }
30 |
31 | public ARIndicatorView(Context context, AttributeSet attrs) {
32 | super(context, attrs);
33 | }
34 |
35 | public ARIndicatorView(Context context, AttributeSet attrs, int defStyle) {
36 | super(context, attrs, defStyle);
37 | }
38 |
39 |
40 | public void attachTo(RecyclerView recyclerView, boolean shouldPage) {
41 | this.recyclerView = recyclerView;
42 |
43 | addIndicators(recyclerView);
44 |
45 | if (shouldPage) {
46 | new PagerSnapHelper().attachToRecyclerView(recyclerView);
47 | }
48 |
49 | this.recyclerView.addOnScrollListener(new OnScrollListener() {
50 | @Override
51 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
52 | super.onScrollStateChanged(recyclerView, newState);
53 | }
54 |
55 | @Override
56 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
57 | super.onScrolled(recyclerView, dx, dy);
58 | if (recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_SETTLING) {
59 | if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) {
60 | int position;
61 | if (dx > 0) {
62 | position = ((LinearLayoutManager) recyclerView.getLayoutManager()).findLastVisibleItemPosition();
63 | } else {
64 | position = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
65 | }
66 | if (position <= indicators.size() - 1) {
67 | if (selectedPosition != position) {
68 | selectIndicatorAt(position);
69 | if (indicatorAnimation != 0) {
70 | animateIndicator(position);
71 | }
72 | }
73 | }
74 | }
75 | }
76 |
77 | }
78 | });
79 | }
80 |
81 | /**
82 | * Attach ARIndicator to ViewPager to know which is the current position in ViewPager and which indicator to be selected
83 | *
84 | * @param viewPager ViewPager to be attached to
85 | */
86 | public void attachTo(ViewPager viewPager) {
87 | this.viewPager = viewPager;
88 |
89 | this.addIndicators(viewPager);
90 |
91 | this.viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
92 | @Override
93 | public void onPageScrolled(int i, float v, int i1) {
94 |
95 | }
96 |
97 | @Override
98 | public void onPageSelected(int i) {
99 | selectIndicatorAt(i);
100 | if (indicatorAnimation != 0) {
101 | animateIndicator(i);
102 | }
103 | }
104 |
105 | @Override
106 | public void onPageScrollStateChanged(int i) {
107 |
108 | }
109 | });
110 | }
111 |
112 |
113 | private void addIndicators(RecyclerView recyclerView) {
114 | if (recyclerView != null) {
115 | RecyclerView.Adapter adapter = recyclerView.getAdapter();
116 | if (adapter != null) {
117 | for (int i = 0; i < adapter.getItemCount(); i++) {
118 | drawCircle(i);
119 | }
120 | } else {
121 | throw new NullPointerException("RecyclerView Adapter not found or null --> ARIndicatorView");
122 | }
123 | } else {
124 | throw new NullPointerException("RecyclerView is null --> ARIndicatorView");
125 | }
126 | }
127 |
128 | private void addIndicators(ViewPager viewPager) {
129 | if (viewPager != null) {
130 | PagerAdapter pagerAdapter = viewPager.getAdapter();
131 | if (pagerAdapter != null) {
132 | for (int i = 0; i < pagerAdapter.getCount(); i++) {
133 | drawCircle(i);
134 | }
135 | } else {
136 | throw new NullPointerException("ViewPager Adapter is null --> ARIndicatorView");
137 | }
138 | } else {
139 | throw new NullPointerException("ViewPager is null --> ARIndicatorView");
140 | }
141 | }
142 |
143 |
144 | private void unSelectIndicators() {
145 | for (int i = 0; i < indicators.size(); i++) {
146 | this.setUnActiveColorTo(indicators.get(i));
147 | }
148 | }
149 |
150 |
151 | private void invalidateIndicators() {
152 | this.removeIndicators();
153 | if (recyclerView != null) {
154 | addIndicators(recyclerView);
155 | } else if (viewPager != null) {
156 | addIndicators(viewPager);
157 | }
158 | this.selectIndicatorAt(this.selectedPosition);
159 | }
160 |
161 | private void selectIndicatorAt(int position) {
162 | this.selectedPosition = position;
163 | this.unSelectIndicators();
164 | this.setActiveColorTo(this.indicators.get(this.selectedPosition));
165 | }
166 |
167 | /**
168 | * Sets the selection color of the indicators
169 | *
170 | * @param selectionColor Integer
171 | */
172 | public void setSelectionColor(int selectionColor) {
173 | this.selectionColor = selectionColor;
174 | this.invalidateIndicators();
175 | }
176 |
177 | /**
178 | * Current indicator selection color
179 | *
180 | * @return Integer
181 | */
182 | public int getSelectionColor() {
183 | return selectionColor;
184 | }
185 |
186 | /**
187 | * Change the indicators size.
188 | *
189 | * @param indicatorSize Integer
190 | */
191 | public void setIndicatorSize(int indicatorSize) {
192 | this.indicatorSize = indicatorSize;
193 | this.invalidateIndicators();
194 | }
195 |
196 | /**
197 | * Current indicator size
198 | *
199 | * @return Integer
200 | */
201 | public int getIndicatorSize() {
202 | return indicatorSize;
203 | }
204 |
205 | /**
206 | * The animation to play when an indicator is selected.
207 | *
208 | * @param indicatorAnimation AnimationId
209 | */
210 | public void setIndicatorAnimation(int indicatorAnimation) {
211 | this.indicatorAnimation = indicatorAnimation;
212 | }
213 |
214 | /**
215 | * Current indicator animation
216 | *
217 | * @return Integer
218 | */
219 | public int getIndicatorAnimation() {
220 | return indicatorAnimation;
221 | }
222 |
223 | /**
224 | * Sets the indicators shape.
225 | *
226 | * You need to pass the drawable id from drawable res.
227 | *
228 | * @param indicatorShape DrawableId
229 | */
230 | public void setIndicatorShape(int indicatorShape) {
231 | this.indicatorShape = indicatorShape;
232 | this.invalidateIndicators();
233 | }
234 |
235 | /**
236 | * Current indicator shape
237 | *
238 | * @return Integer
239 | */
240 | public int getIndicatorShape() {
241 | return indicatorShape;
242 | }
243 |
244 | /**
245 | * This is used for setting the color on the indicators when they are not selected
246 | *
247 | * @param indicatorColor The color to be set to indicators
248 | */
249 | public void setIndicatorColor(int indicatorColor) {
250 | this.indicatorColor = indicatorColor;
251 | this.invalidateIndicators();
252 | }
253 |
254 | /**
255 | * Current indicator color
256 | *
257 | * @return Integer
258 | */
259 | public int getIndicatorColor() {
260 | return indicatorColor;
261 | }
262 |
263 | /**
264 | * Selects the indicator at the given position.
265 | *
266 | * @param position Position to select
267 | */
268 | public void setSelectedPosition(int position) {
269 | this.selectedPosition = position;
270 | this.unSelectIndicators();
271 | this.setActiveColorTo(this.indicators.get(this.selectedPosition));
272 | this.scrollToPosition(selectedPosition);
273 | }
274 |
275 | /**
276 | * Current position of indicator
277 | *
278 | * @return Integer
279 | */
280 | public int getSelectedPosition() {
281 | return selectedPosition;
282 | }
283 |
284 | /**
285 | * Returns numberOfIndicators if you set it via the method setNumberOfDots else 0
286 | *
287 | * @return Integer
288 | */
289 | public int getNumberOfIndicators() {
290 | return numberOfIndicators;
291 | }
292 |
293 | /**
294 | * This is used for adding indicators if you don't want to be attached to RecyclerView or ViewPager
295 | *
296 | * @param numberOfIndicators Integer number of indicators to be added
297 | */
298 | public void setNumberOfIndicators(int numberOfIndicators) {
299 | this.numberOfIndicators = numberOfIndicators;
300 | if (!this.indicators.isEmpty()) {
301 | this.removeIndicators();
302 | }
303 | for (int i = 0; i < this.numberOfIndicators; i++) {
304 | drawCircle(i);
305 | }
306 | }
307 |
308 | /**
309 | * Removes all indicators
310 | */
311 | public void removeIndicators() {
312 | for (ImageView imageView : indicators) {
313 | removeView(imageView);
314 | }
315 | indicators.clear();
316 | }
317 |
318 |
319 | @Override
320 | public boolean dispatchTouchEvent(MotionEvent ev) {
321 | if (ev.getAction() == MotionEvent.ACTION_DOWN) {
322 | return this.isScrubbingEnabled;
323 | }
324 | this.selectIndicatorWhenScrubbing(ev);
325 | return this.isScrubbingEnabled;
326 | }
327 |
328 | private void selectIndicatorWhenScrubbing(MotionEvent ev) {
329 |
330 | int x = Math.round(ev.getX());
331 | int y = Math.round(ev.getY());
332 |
333 | for (int i = 0; i < getChildCount(); i++) {
334 | ImageView child = (ImageView) getChildAt(i);
335 | if (x > child.getLeft() && x < child.getRight() && y > child.getTop() && y < child.getBottom()) {
336 | this.isScrubbing = true;
337 | this.selectIndicatorAt(i);
338 | this.scrollToPosition(i);
339 | }
340 | }
341 | }
342 |
343 | private void scrollToPosition(int position) {
344 | if (this.recyclerView != null) {
345 | this.selectedPosition = position;
346 | this.recyclerView.smoothScrollToPosition(position);
347 | } else if (this.viewPager != null) {
348 | this.selectedPosition = position;
349 | this.viewPager.setCurrentItem(position, true);
350 | }
351 | }
352 |
353 | private void animateIndicator(int position) {
354 | if (this.isScrubbingEnabled && this.isScrubbing) {
355 | if (this.shouldAnimateOnScrubbing) {
356 | indicators.get(position).startAnimation(AnimationUtils.loadAnimation(getContext(), indicatorAnimation));
357 | }
358 | } else {
359 | indicators.get(position).startAnimation(AnimationUtils.loadAnimation(getContext(), indicatorAnimation));
360 | }
361 | }
362 | }
363 |
--------------------------------------------------------------------------------