com.github.pockethub.android.ui.view.DotPageIndicator.java Source code

Java tutorial

Introduction

Here is the source code for com.github.pockethub.android.ui.view.DotPageIndicator.java

Source

/*
 * Copyright (c) 2015 PocketHub
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.pockethub.android.ui.view;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

import com.github.pockethub.android.R;
import com.github.pockethub.android.ui.ViewPager;

public class DotPageIndicator extends View implements android.support.v4.view.ViewPager.OnPageChangeListener {

    private static final Paint PAINT_SELECTED = new Paint();
    private static final Paint PAINT_NOT_SELECTED = new Paint();
    /**
     * Radius of a dot
     */
    private float dotRadius;

    /**
     * Spacing between dots, can be set by app:spacing in the xml
     */
    private float dotSpacing;

    /**
     * The width without the padding, set in onSizedChanged
     */
    private float currentWidth;

    /**
     * The height without the padding, set in onSizedChanged
     */
    private float currentHeight;

    /**
     * The ViewPager which is currently set
     */
    private ViewPager viewPager;

    /**
     * If the drawing values needs to change (Only if size changed)
     */
    private boolean updateValues;

    /**
     * Number of dots per row
     */
    private int dotColumns;

    /**
     * Number of rows in the view
     */
    private int dotRows;

    /**
     * The standard alpha from the selected dot color
     */
    private int dotAlpha;

    /**
     * Fields for when the dot is moving
     */
    private float amount;
    private int currentPos;
    private int nextPos;

    /**
     * Instantiates the view and it's values
     * @param context
     * @param attrs
     */
    public DotPageIndicator(Context context, AttributeSet attrs) {
        super(context, attrs);

        if (isInEditMode()) {
            return;
        }

        Resources res = getResources();
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DotPageIndicator, 0, 0);

        try {
            PAINT_SELECTED.setColor(a.getColor(R.styleable.DotPageIndicator_dotColorSelected,
                    getColor(res, android.R.color.secondary_text_light, context.getTheme())));

            PAINT_NOT_SELECTED.setColor(a.getColor(R.styleable.DotPageIndicator_dotColor,
                    getColor(res, android.R.color.secondary_text_dark, context.getTheme())));

            dotSpacing = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, res.getDisplayMetrics());
            dotRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, res.getDisplayMetrics());

            dotRadius = a.getDimensionPixelSize(R.styleable.DotPageIndicator_dotRadius, (int) dotRadius);
            dotSpacing = a.getDimensionPixelSize(R.styleable.DotPageIndicator_dotSpacing, (int) dotSpacing);
        } finally {
            a.recycle();
        }
        dotAlpha = PAINT_SELECTED.getAlpha();
    }

    public void setDotColor(@ColorInt int color) {
        PAINT_NOT_SELECTED.setColor(color);
    }

    public void setSelectedDotColor(@ColorInt int color) {
        PAINT_SELECTED.setColor(color);
    }

    /**
     * Sets up the size of the view and saves it for further use
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (viewPager != null) {
            createOnDrawValues();
        }

        int desiredWidth = (int) getWidthOfDots(dotColumns);
        int desiredHeight = (int) getHeightOfDots(dotRows);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width;
        int height;

        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else if (widthMode == MeasureSpec.AT_MOST) {
            width = Math.min(desiredWidth, widthSize);
        } else {
            width = desiredWidth;
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else if (heightMode == MeasureSpec.AT_MOST) {
            height = Math.min(desiredHeight, heightSize);
        } else {
            height = desiredHeight;
        }

        setMeasuredDimension(width, height);

        float horiPadding = getPaddingLeft() + getPaddingRight();
        float vertPadding = getPaddingBottom() + getPaddingTop();

        currentWidth = width - horiPadding;
        currentHeight = height - vertPadding;
        updateValues = true;
    }

    /**
     * Updates values for drawing
     */
    private void createOnDrawValues() {
        int pageAmount = getNumberOfPages();
        float w = getWidthOfDots(pageAmount);

        dotRows = 1;
        dotColumns = pageAmount;

        if (currentWidth > 0 && w > currentWidth) {
            for (int i = 1; i < pageAmount; i++) {
                float tempWidth = currentWidth - getWidthOfDots(i);
                if (tempWidth <= 0) {
                    dotRows = (int) Math.ceil(pageAmount / i - 1);
                    dotColumns = i - 1;
                    break;
                }
            }

            if (getHeightOfDots(dotRows) > currentHeight) {
                throw new IllegalStateException("Can't fit DotPageIndicator");
            }
        }
        updateValues = false;
    }

    /**
     * Draws the dots
     *
     * @param canvas The canvas to draw on
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (updateValues) {
            createOnDrawValues();
        }

        for (int i = 0; i < dotRows; i++) {
            int dotDiff = getNumberOfPages() - ((i + 1) * dotColumns);
            int dots = dotDiff < 0 ? dotDiff + dotColumns : dotColumns;
            float rowCenterY = (i * dotRadius * 2) + (i * dotSpacing) + dotRadius;

            drawRow(canvas, dots, currentWidth, rowCenterY);
        }

        drawSelectedDot(canvas);
    }

    /**
     * Draw the selected dot and when it moves
     *
     * @param canvas The canvas to draw on
     */

    private void drawSelectedDot(Canvas canvas) {
        float distance = (dotRadius * 2 + dotSpacing) * amount;

        float fromPosX = getDotCenterX(currentPos);
        float fromPosY = getDotCenterY(currentPos);

        float toPosX = getDotCenterX(nextPos);
        float toPosY = getDotCenterY(nextPos);

        if (toPosY != fromPosY) {
            PAINT_SELECTED.setAlpha(Math.round(dotAlpha * amount));
            canvas.drawCircle(toPosX - ((dotRadius * 2 + dotSpacing) - distance), toPosY, dotRadius,
                    PAINT_SELECTED);
            PAINT_SELECTED.setAlpha(Math.round(dotAlpha * (1 - amount)));
        }

        canvas.drawCircle(fromPosX + distance, fromPosY, dotRadius, PAINT_SELECTED);
        PAINT_SELECTED.setAlpha(dotAlpha);
    }

    /**
     * Draws a row of dots
     *
     * @param canvas The canvas to draw on
     * @param columns Number of dots in the row
     * @param w Width of the view to draw on
     * @param y Y position of the row (center)
     */
    private void drawRow(Canvas canvas, int columns, float w, float y) {
        float center = w / 2;
        float start = center - getWidthOfDots(columns / 2);
        start -= columns % 2 == 0 ? dotSpacing / 2 : dotRadius + dotSpacing;

        for (int i = 0; i < columns; i++) {
            canvas.drawCircle(start + dotRadius, y, dotRadius, PAINT_NOT_SELECTED);
            start += (dotRadius * 2) + dotSpacing;
        }
    }

    /**
     * A helper method for getting colors from resource id
     *
     * @param res A reference to resources
     * @param id Resource id to the color
     * @param theme A theme reference
     * @return The color specified
     */
    private int getColor(Resources res, int id, Resources.Theme theme) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            return res.getColor(id, theme);
        } else {
            return res.getColor(id);
        }
    }

    /**
     * Gets the center x position of the dot in position {@param index}
     *
     * @param index The dots index
     * @return The x position
     */
    private float getDotCenterX(int index) {
        int column = index % dotColumns;
        int row = (index - column) / dotColumns;
        int columns = dotColumns;

        if (dotColumns * (row + 1) > getNumberOfPages()) {
            columns = dotColumns - ((dotColumns * (row + 1)) - getNumberOfPages());
        }

        float center = currentWidth / 2;
        float start = center - getWidthOfDots(columns / 2);

        start -= columns % 2 == 0 ? dotSpacing / 2 : dotRadius + dotSpacing;
        start += ((dotRadius * 2) + dotSpacing) * column;

        return start + dotRadius;
    }

    /**
     * Gets the center y position of the dot in position {@param index}
     *
     * @param index The dots index
     * @return The y position
     */
    private float getDotCenterY(int index) {
        int column = index % dotColumns;
        int row = (index - column) / dotColumns;

        return (row * dotRadius * 2) + (row * dotSpacing) + dotRadius;
    }

    /**
     * Gets the width of some dots
     *
     * @param amount The amount of dots to measure
     * @return The calculated width
     */
    private float getWidthOfDots(int amount) {
        return (amount * dotRadius * 2) + ((amount - 1) * dotSpacing);
    }

    /**
     * Gets the height of some rows of dots
     *
     * @param rows The rows of dots to measure
     * @return The calculated height
     */
    private float getHeightOfDots(int rows) {
        return (rows * dotRadius * 2) + ((rows - 1) * dotSpacing);
    }

    /**
     * The amount of pages in the {@link ViewPager}
     *
     * @return Number of pages
     */
    private int getNumberOfPages() {
        return viewPager != null ? viewPager.getAdapter().getCount() : -1;
    }

    /**
     * Sets the viewpager to use
     *
     * @param pager ViewPager
     */
    public void setViewPager(ViewPager pager) {
        if (pager == null) {
            return;
        }

        if (viewPager != null) {
            viewPager.removeOnPageChangeListener(this);
        }

        viewPager = pager;
        pager.addOnPageChangeListener(this);
    }

    /**
     * Animates the dot
     *
     * @param position
     * @param positionOffset
     * @param positionOffsetPixels
     */
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        amount = positionOffset;
        currentPos = positionOffsetPixels >= 0 ? position : position + 1;
        nextPos = positionOffsetPixels >= 0 ? position + 1 : position;

        invalidate();
    }

    @Override
    public void onPageSelected(int position) {
        //Not used
    }

    /**
     * Sets current item
     *
     * @param state
     */
    @Override
    public void onPageScrollStateChanged(int state) {
        if (state == ViewPager.SCROLL_STATE_IDLE || state == ViewPager.SCROLL_STATE_SETTLING) {
            currentPos = viewPager.getCurrentItem();

        }
    }
}