Skip to content

Commit

Permalink
Add support for custom first/last page indicators (#46)
Browse files Browse the repository at this point in the history
* Add support for custom first/last page indicators

* Fix custom first/last dot drawables in vertical indicators
  • Loading branch information
koral-- authored Oct 20, 2022
1 parent 8213a4b commit aae980d
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 17 deletions.
27 changes: 15 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Supports ViewPager, RecyclerView and attaching to custom pagers.

![preview](readmeAssets/demo.gif)
![preview](readmeAssets/demo-horizontal.gif)
![Custom drawables](readmeAssets/custom-drawables.gif)

## Getting started
Add dependency to Gradle script:
Expand Down Expand Up @@ -108,18 +109,20 @@ And then you can attach your pager like this:
indicator.attachToPager(pager, new ViewPagerAttacher());
```
## Customization
| Attribute| Explanation| Default Value |
|-----------------------|-----------------------|--------|
| spi_dotSize| The diameter of a dot.| ```6dp```|
| spi_dotSelectedSize| The diameter of a currently selected dot.| ```10dp```|
| spi_dotColor | The color of a dot. | ```@android:color/darker_gray```|
| spi_dotSelectedColor| The color of the currently selected dot.| ```@android:color/darker_gray``` |
| spi_dotSpacing | The distance from center to center of each dot. | ```8dp``` |
| spi_visibleDotCount | The maximum number of dots which will be visible at the same time. If pager has more pages than visible_dot_count, indicator will scroll to show extra dots. Must be odd number. | ```5``` |
| spi_visibleDotThreshold | The minimum number of dots which should be visible. If pager has less pages than visibleDotThreshold, no dots will be shown. | ```2``` |
| spi_looped | The mode for looped pagers support. You should make indicator looped if your custom pager is looped too. If pager has less items than ```spi_visibleDotCount```, indicator will work as usual; otherwise it will always be in infinite state. | ```false```|
| spi_dotMinimumSize | The minimum dot size for the corner dots. This size is lower or equal to ```spi_dotSize``` and greater or equal to the internal calculation for the corner dots. | Internal calculation based on ```spi_dotSize```, ```spi_dotSelectedSize``` and ```spi_dotSpacing``` |
| spi_orientation | Visible orientation of the dots | LinearLayoutManager.HORIZONTAL |
| Attribute | Explanation | Default Value |
|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
| spi_dotSize | The diameter of a dot. | ```6dp``` |
| spi_dotSelectedSize | The diameter of a currently selected dot. | ```10dp``` |
| spi_dotColor | The color of a dot. | ```@android:color/darker_gray``` |
| spi_dotSelectedColor | The color of the currently selected dot. | ```@android:color/darker_gray``` |
| spi_dotSpacing | The distance from center to center of each dot. | ```8dp``` |
| spi_visibleDotCount | The maximum number of dots which will be visible at the same time. If pager has more pages than visible_dot_count, indicator will scroll to show extra dots. Must be odd number. | ```5``` |
| spi_visibleDotThreshold | The minimum number of dots which should be visible. If pager has less pages than visibleDotThreshold, no dots will be shown. | ```2``` |
| spi_looped | The mode for looped pagers support. You should make indicator looped if your custom pager is looped too. If pager has less items than ```spi_visibleDotCount```, indicator will work as usual; otherwise it will always be in infinite state. | ```false``` |
| spi_dotMinimumSize | The minimum dot size for the corner dots. This size is lower or equal to ```spi_dotSize``` and greater or equal to the internal calculation for the corner dots. | Internal calculation based on ```spi_dotSize```, ```spi_dotSelectedSize``` and ```spi_dotSpacing``` |
| spi_orientation | Visible orientation of the dots | LinearLayoutManager.HORIZONTAL |
| spi_firstDotDrawable | Custom drawable of the first dot, color is tinted and size is changed like standard dots. If first dot is also the last one then this value is taken into account. | `null` |
| spi_lastDotDrawable | Custom drawable of the last dot, color is tinted and size is changed like standard dots. | `null` |

## TODO
1. Some extreme customizations may work incorrect.
Expand Down
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 1.2.4
- Add support for custom first and last page indicators

## 1.2.3
- updated target sdk version to 31

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ protected void onCreate(Bundle savedInstanceState) {
recyclerView.setPadding(screenWidth / 3, 0, screenWidth / 3, 0);

ScrollingPagerIndicator recyclerIndicator = findViewById(R.id.recycler_indicator);
ScrollingPagerIndicator customRecyclerIndicator = findViewById(R.id.custom_recycler_indicator);
// Consider page in the middle current
recyclerIndicator.attachToRecyclerView(recyclerView);
customRecyclerIndicator.attachToRecyclerView(recyclerView);

SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);
Expand Down
5 changes: 5 additions & 0 deletions demo/src/main/res/drawable/baseline_accessibility_new_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFF" android:pathData="M20.5,6c-2.61,0.7 -5.67,1 -8.5,1s-5.89,-0.3 -8.5,-1L3,8c1.86,0.5 4,0.83 6,1v13h2v-6h2v6h2V9c2,-0.17 4.14,-0.5 6,-1l-0.5,-2zM12,6c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z"/>
</vector>
5 changes: 5 additions & 0 deletions demo/src/main/res/drawable/baseline_free_breakfast_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFF" android:pathData="M20,3L4,3v10c0,2.21 1.79,4 4,4h6c2.21,0 4,-1.79 4,-4v-3h2c1.11,0 2,-0.9 2,-2L22,5c0,-1.11 -0.89,-2 -2,-2zM20,8h-2L18,5h2v3zM4,19h16v2L4,21z"/>
</vector>
18 changes: 17 additions & 1 deletion demo/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/pager_title"
app:spi_dotColor="@color/dotNormal"
app:spi_dotSelectedColor="@color/dotHighlight" />
app:spi_dotSelectedColor="@color/dotHighlight"
app:spi_lastDotDrawable="@drawable/baseline_free_breakfast_24"
app:spi_firstDotDrawable="@drawable/baseline_accessibility_new_24" />

<androidx.viewpager.widget.ViewPager
android:id="@+id/pager"
Expand Down Expand Up @@ -92,6 +94,20 @@
app:spi_dotSelectedColor="@color/dotHighlight"
app:spi_orientation="vertical" />

<ru.tinkoff.scrollingpagerindicator.ScrollingPagerIndicator
android:id="@+id/custom_recycler_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="32dp"
app:layout_constraintBottom_toBottomOf="@id/recycler"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/recycler"
app:spi_lastDotDrawable="@drawable/baseline_free_breakfast_24"
app:spi_firstDotDrawable="@drawable/baseline_accessibility_new_24"
app:spi_dotColor="@color/dotNormal"
app:spi_dotSelectedColor="@color/dotHighlight"
app:spi_orientation="vertical" />

<TextView
android:id="@+id/page_count_label"
android:layout_width="100dp"
Expand Down
Binary file added readmeAssets/custom-drawables.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.SparseArray;
Expand All @@ -26,7 +27,7 @@
public class ScrollingPagerIndicator extends View {

@IntDef({RecyclerView.HORIZONTAL, RecyclerView.VERTICAL})
public @interface Orientation{};
public @interface Orientation{}

private int infiniteDotCount;

Expand Down Expand Up @@ -55,6 +56,12 @@ public class ScrollingPagerIndicator extends View {
@ColorInt
private int selectedDotColor;

@Nullable
private final Drawable firstDotDrawable;

@Nullable
private final Drawable lastDotDrawable;

private boolean looped;

private Runnable attachRunnable;
Expand Down Expand Up @@ -89,6 +96,9 @@ public ScrollingPagerIndicator(Context context, @Nullable AttributeSet attrs, in
setVisibleDotCount(visibleDotCount);
visibleDotThreshold = attributes.getInt(R.styleable.ScrollingPagerIndicator_spi_visibleDotThreshold, 2);
orientation = attributes.getInt(R.styleable.ScrollingPagerIndicator_spi_orientation, RecyclerView.HORIZONTAL);

firstDotDrawable = attributes.getDrawable(R.styleable.ScrollingPagerIndicator_spi_firstDotDrawable);
lastDotDrawable = attributes.getDrawable(R.styleable.ScrollingPagerIndicator_spi_lastDotDrawable);
attributes.recycle();

paint = new Paint();
Expand Down Expand Up @@ -358,8 +368,7 @@ public void onPageScrolled(int page, float offset) {
} else if (itemCount > 1) {
scaleDotByOffset(0, 1 - offset);
}
}
else { // Vertical orientation
} else { // Vertical orientation
scaleDotByOffset(page - 1, offset);
scaleDotByOffset(page, 1 - offset);
}
Expand Down Expand Up @@ -551,7 +560,31 @@ protected void onDraw(Canvas canvas) {
}

paint.setColor(calculateDotColor(scale));
if (orientation == LinearLayoutManager.HORIZONTAL) {
final Drawable dotDrawable;
if (i == firstVisibleDotPos) {
dotDrawable = firstDotDrawable;
} else if (i == lastVisibleDotPos) {
dotDrawable = lastDotDrawable;
} else {
dotDrawable = null;
}
if (dotDrawable != null) {
if (orientation == LinearLayoutManager.HORIZONTAL) {
dotDrawable.setBounds((int) (dot - visibleFramePosition - dotSelectedSize / 2),
getMeasuredHeight() / 2 - dotSelectedSize / 2,
(int) (dot - visibleFramePosition + dotSelectedSize / 2),
getMeasuredHeight() / 2 + dotSelectedSize / 2);
} else {
dotDrawable.setBounds(getMeasuredWidth() / 2 - dotSelectedSize / 2,
(int) (dot - visibleFramePosition - dotSelectedSize / 2),
getMeasuredWidth() / 2 + dotSelectedSize / 2,
(int) (dot - visibleFramePosition + dotSelectedSize / 2));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
dotDrawable.setTint(paint.getColor());
}
dotDrawable.draw(canvas);
} else if (orientation == LinearLayoutManager.HORIZONTAL) {
float cx = dot - visibleFramePosition;
if (autoRtl && isRtl()) {
cx = getWidth() - cx;
Expand Down
2 changes: 2 additions & 0 deletions scrollingpagerindicator/src/main/res/values/attrs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
<attr name="spi_firstDotDrawable" format="reference" />
<attr name="spi_lastDotDrawable" format="reference" />
</declare-styleable>
</resources>

0 comments on commit aae980d

Please sign in to comment.