Add Spotless

This commit is contained in:
Hemanth S 2020-10-06 14:16:04 +05:30
parent 2af13a4e6c
commit defcd86152
286 changed files with 15604 additions and 13757 deletions

View file

@ -19,184 +19,173 @@ import android.content.res.TypedArray;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import androidx.annotation.FontRes;
import com.google.android.material.textview.MaterialTextView;
import code.name.monkey.retromusic.R;
import com.google.android.material.textview.MaterialTextView;
public class BaselineGridTextView extends MaterialTextView {
private final float FOUR_DIP;
private final float FOUR_DIP;
private int extraBottomPadding = 0;
private int extraBottomPadding = 0;
private int extraTopPadding = 0;
private int extraTopPadding = 0;
private @FontRes
int fontResId = 0;
private @FontRes int fontResId = 0;
private float lineHeightHint = 0f;
private float lineHeightHint = 0f;
private float lineHeightMultiplierHint = 1f;
private float lineHeightMultiplierHint = 1f;
private boolean maxLinesByHeight = false;
private boolean maxLinesByHeight = false;
public BaselineGridTextView(Context context) {
this(context, null);
public BaselineGridTextView(Context context) {
this(context, null);
}
public BaselineGridTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
public BaselineGridTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final TypedArray a =
context.obtainStyledAttributes(attrs, R.styleable.BaselineGridTextView, defStyleAttr, 0);
// first check TextAppearance for line height & font attributes
if (a.hasValue(R.styleable.BaselineGridTextView_android_textAppearance)) {
int textAppearanceId =
a.getResourceId(
R.styleable.BaselineGridTextView_android_textAppearance,
android.R.style.TextAppearance);
TypedArray ta =
context.obtainStyledAttributes(textAppearanceId, R.styleable.BaselineGridTextView);
parseTextAttrs(ta);
ta.recycle();
}
public BaselineGridTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
// then check view attrs
parseTextAttrs(a);
maxLinesByHeight = a.getBoolean(R.styleable.BaselineGridTextView_maxLinesByHeight, false);
a.recycle();
FOUR_DIP =
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());
computeLineHeight();
}
@Override
public int getCompoundPaddingBottom() {
// include extra padding to make the height a multiple of 4dp
return super.getCompoundPaddingBottom() + extraBottomPadding;
}
@Override
public int getCompoundPaddingTop() {
// include extra padding to place the first line's baseline on the grid
return super.getCompoundPaddingTop() + extraTopPadding;
}
public @FontRes int getFontResId() {
return fontResId;
}
public float getLineHeightHint() {
return lineHeightHint;
}
public void setLineHeightHint(float lineHeightHint) {
this.lineHeightHint = lineHeightHint;
computeLineHeight();
}
public float getLineHeightMultiplierHint() {
return lineHeightMultiplierHint;
}
public void setLineHeightMultiplierHint(float lineHeightMultiplierHint) {
this.lineHeightMultiplierHint = lineHeightMultiplierHint;
computeLineHeight();
}
public boolean getMaxLinesByHeight() {
return maxLinesByHeight;
}
public void setMaxLinesByHeight(boolean maxLinesByHeight) {
this.maxLinesByHeight = maxLinesByHeight;
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
extraTopPadding = 0;
extraBottomPadding = 0;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = getMeasuredHeight();
height += ensureBaselineOnGrid();
height += ensureHeightGridAligned(height);
setMeasuredDimension(getMeasuredWidth(), height);
checkMaxLines(height, MeasureSpec.getMode(heightMeasureSpec));
}
/**
* When measured with an exact height, text can be vertically clipped mid-line. Prevent this by
* setting the {@code maxLines} property based on the available space.
*/
private void checkMaxLines(int height, int heightMode) {
if (!maxLinesByHeight || heightMode != MeasureSpec.EXACTLY) {
return;
}
public BaselineGridTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
int textHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
int completeLines = (int) Math.floor(textHeight / getLineHeight());
setMaxLines(completeLines);
}
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.BaselineGridTextView, defStyleAttr, 0);
/** Ensures line height is a multiple of 4dp. */
private void computeLineHeight() {
final Paint.FontMetrics fm = getPaint().getFontMetrics();
final float fontHeight = Math.abs(fm.ascent - fm.descent) + fm.leading;
final float desiredLineHeight =
(lineHeightHint > 0) ? lineHeightHint : lineHeightMultiplierHint * fontHeight;
// first check TextAppearance for line height & font attributes
if (a.hasValue(R.styleable.BaselineGridTextView_android_textAppearance)) {
int textAppearanceId =
a.getResourceId(R.styleable.BaselineGridTextView_android_textAppearance,
android.R.style.TextAppearance);
TypedArray ta = context.obtainStyledAttributes(
textAppearanceId, R.styleable.BaselineGridTextView);
parseTextAttrs(ta);
ta.recycle();
}
final int baselineAlignedLineHeight =
(int) ((FOUR_DIP * (float) Math.ceil(desiredLineHeight / FOUR_DIP)) + 0.5f);
setLineSpacing(baselineAlignedLineHeight - fontHeight, 1f);
}
// then check view attrs
parseTextAttrs(a);
maxLinesByHeight = a.getBoolean(R.styleable.BaselineGridTextView_maxLinesByHeight, false);
a.recycle();
FOUR_DIP = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());
computeLineHeight();
/** Ensure that the first line of text sits on the 4dp grid. */
private int ensureBaselineOnGrid() {
float baseline = getBaseline();
float gridAlign = baseline % FOUR_DIP;
if (gridAlign != 0) {
extraTopPadding = (int) (FOUR_DIP - Math.ceil(gridAlign));
}
return extraTopPadding;
}
@Override
public int getCompoundPaddingBottom() {
// include extra padding to make the height a multiple of 4dp
return super.getCompoundPaddingBottom() + extraBottomPadding;
/** Ensure that height is a multiple of 4dp. */
private int ensureHeightGridAligned(int height) {
float gridOverhang = height % FOUR_DIP;
if (gridOverhang != 0) {
extraBottomPadding = (int) (FOUR_DIP - Math.ceil(gridOverhang));
}
return extraBottomPadding;
}
@Override
public int getCompoundPaddingTop() {
// include extra padding to place the first line's baseline on the grid
return super.getCompoundPaddingTop() + extraTopPadding;
private void parseTextAttrs(TypedArray a) {
if (a.hasValue(R.styleable.BaselineGridTextView_lineHeightMultiplierHint)) {
lineHeightMultiplierHint =
a.getFloat(R.styleable.BaselineGridTextView_lineHeightMultiplierHint, 1f);
}
public @FontRes
int getFontResId() {
return fontResId;
if (a.hasValue(R.styleable.BaselineGridTextView_lineHeightHint)) {
lineHeightHint = a.getDimensionPixelSize(R.styleable.BaselineGridTextView_lineHeightHint, 0);
}
public float getLineHeightHint() {
return lineHeightHint;
if (a.hasValue(R.styleable.BaselineGridTextView_android_fontFamily)) {
fontResId = a.getResourceId(R.styleable.BaselineGridTextView_android_fontFamily, 0);
}
public void setLineHeightHint(float lineHeightHint) {
this.lineHeightHint = lineHeightHint;
computeLineHeight();
}
public float getLineHeightMultiplierHint() {
return lineHeightMultiplierHint;
}
public void setLineHeightMultiplierHint(float lineHeightMultiplierHint) {
this.lineHeightMultiplierHint = lineHeightMultiplierHint;
computeLineHeight();
}
public boolean getMaxLinesByHeight() {
return maxLinesByHeight;
}
public void setMaxLinesByHeight(boolean maxLinesByHeight) {
this.maxLinesByHeight = maxLinesByHeight;
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
extraTopPadding = 0;
extraBottomPadding = 0;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = getMeasuredHeight();
height += ensureBaselineOnGrid();
height += ensureHeightGridAligned(height);
setMeasuredDimension(getMeasuredWidth(), height);
checkMaxLines(height, MeasureSpec.getMode(heightMeasureSpec));
}
/**
* When measured with an exact height, text can be vertically clipped mid-line. Prevent
* this by setting the {@code maxLines} property based on the available space.
*/
private void checkMaxLines(int height, int heightMode) {
if (!maxLinesByHeight || heightMode != MeasureSpec.EXACTLY) {
return;
}
int textHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
int completeLines = (int) Math.floor(textHeight / getLineHeight());
setMaxLines(completeLines);
}
/**
* Ensures line height is a multiple of 4dp.
*/
private void computeLineHeight() {
final Paint.FontMetrics fm = getPaint().getFontMetrics();
final float fontHeight = Math.abs(fm.ascent - fm.descent) + fm.leading;
final float desiredLineHeight = (lineHeightHint > 0)
? lineHeightHint
: lineHeightMultiplierHint * fontHeight;
final int baselineAlignedLineHeight =
(int) ((FOUR_DIP * (float) Math.ceil(desiredLineHeight / FOUR_DIP)) + 0.5f);
setLineSpacing(baselineAlignedLineHeight - fontHeight, 1f);
}
/**
* Ensure that the first line of text sits on the 4dp grid.
*/
private int ensureBaselineOnGrid() {
float baseline = getBaseline();
float gridAlign = baseline % FOUR_DIP;
if (gridAlign != 0) {
extraTopPadding = (int) (FOUR_DIP - Math.ceil(gridAlign));
}
return extraTopPadding;
}
/**
* Ensure that height is a multiple of 4dp.
*/
private int ensureHeightGridAligned(int height) {
float gridOverhang = height % FOUR_DIP;
if (gridOverhang != 0) {
extraBottomPadding = (int) (FOUR_DIP - Math.ceil(gridOverhang));
}
return extraBottomPadding;
}
private void parseTextAttrs(TypedArray a) {
if (a.hasValue(R.styleable.BaselineGridTextView_lineHeightMultiplierHint)) {
lineHeightMultiplierHint =
a.getFloat(R.styleable.BaselineGridTextView_lineHeightMultiplierHint, 1f);
}
if (a.hasValue(R.styleable.BaselineGridTextView_lineHeightHint)) {
lineHeightHint = a.getDimensionPixelSize(
R.styleable.BaselineGridTextView_lineHeightHint, 0);
}
if (a.hasValue(R.styleable.BaselineGridTextView_android_fontFamily)) {
fontResId = a.getResourceId(R.styleable.BaselineGridTextView_android_fontFamily, 0);
}
}
}
}
}

View file

@ -26,422 +26,429 @@ import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import code.name.monkey.appthemehelper.util.ATHUtil;
import code.name.monkey.retromusic.R;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import code.name.monkey.appthemehelper.util.ATHUtil;
import code.name.monkey.retromusic.R;
/**
* @author Aidan Follestad (afollestad), modified for Phonograph by Karim Abou Zeid (kabouzeid)
*/
/** @author Aidan Follestad (afollestad), modified for Phonograph by Karim Abou Zeid (kabouzeid) */
public class BreadCrumbLayout extends HorizontalScrollView implements View.OnClickListener {
@ColorInt
private int contentColorActivated;
@ColorInt
private int contentColorDeactivated;
private int mActive;
private SelectionCallback mCallback;
private LinearLayout mChildFrame;
// Stores currently visible crumbs
private List<Crumb> mCrumbs;
// Stores user's navigation history, like a fragment back stack
private List<Crumb> mHistory;
// Used in setActiveOrAdd() between clearing crumbs and adding the new set, nullified afterwards
private List<Crumb> mOldCrumbs;
@ColorInt private int contentColorActivated;
@ColorInt private int contentColorDeactivated;
private int mActive;
private SelectionCallback mCallback;
private LinearLayout mChildFrame;
// Stores currently visible crumbs
private List<Crumb> mCrumbs;
// Stores user's navigation history, like a fragment back stack
private List<Crumb> mHistory;
// Used in setActiveOrAdd() between clearing crumbs and adding the new set, nullified afterwards
private List<Crumb> mOldCrumbs;
public BreadCrumbLayout(Context context) {
super(context);
init();
public BreadCrumbLayout(Context context) {
super(context);
init();
}
public BreadCrumbLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public BreadCrumbLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void addCrumb(@NonNull Crumb crumb, boolean refreshLayout) {
LinearLayout view =
(LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.bread_crumb, this, false);
view.setTag(mCrumbs.size());
view.setOnClickListener(this);
ImageView iv = (ImageView) view.getChildAt(1);
if (iv.getDrawable() != null) {
iv.getDrawable().setAutoMirrored(true);
}
iv.setVisibility(View.GONE);
public BreadCrumbLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
mChildFrame.addView(
view,
new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
mCrumbs.add(crumb);
if (refreshLayout) {
mActive = mCrumbs.size() - 1;
requestLayout();
}
invalidateActivatedAll();
}
public BreadCrumbLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
public void addHistory(Crumb crumb) {
mHistory.add(crumb);
}
public void clearCrumbs() {
try {
mOldCrumbs = new ArrayList<>(mCrumbs);
mCrumbs.clear();
mChildFrame.removeAllViews();
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
public void addCrumb(@NonNull Crumb crumb, boolean refreshLayout) {
LinearLayout view = (LinearLayout) LayoutInflater.from(getContext())
.inflate(R.layout.bread_crumb, this, false);
view.setTag(mCrumbs.size());
view.setOnClickListener(this);
public void clearHistory() {
mHistory.clear();
}
ImageView iv = (ImageView) view.getChildAt(1);
if (iv.getDrawable() != null) {
iv.getDrawable().setAutoMirrored(true);
}
iv.setVisibility(View.GONE);
mChildFrame.addView(view, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
mCrumbs.add(crumb);
if (refreshLayout) {
mActive = mCrumbs.size() - 1;
requestLayout();
}
invalidateActivatedAll();
public Crumb findCrumb(@NonNull File forDir) {
for (int i = 0; i < mCrumbs.size(); i++) {
if (mCrumbs.get(i).getFile().equals(forDir)) {
return mCrumbs.get(i);
}
}
return null;
}
public void addHistory(Crumb crumb) {
mHistory.add(crumb);
public int getActiveIndex() {
return mActive;
}
public Crumb getCrumb(int index) {
return mCrumbs.get(index);
}
public SavedStateWrapper getStateWrapper() {
return new SavedStateWrapper(this);
}
public int historySize() {
return mHistory.size();
}
public Crumb lastHistory() {
if (mHistory.size() == 0) {
return null;
}
return mHistory.get(mHistory.size() - 1);
}
public void clearCrumbs() {
try {
mOldCrumbs = new ArrayList<>(mCrumbs);
mCrumbs.clear();
mChildFrame.removeAllViews();
} catch (IllegalStateException e) {
e.printStackTrace();
}
@Override
public void onClick(View v) {
if (mCallback != null) {
int index = (Integer) v.getTag();
mCallback.onCrumbSelection(mCrumbs.get(index), index);
}
}
public void clearHistory() {
mHistory.clear();
public boolean popHistory() {
if (mHistory.size() == 0) {
return false;
}
mHistory.remove(mHistory.size() - 1);
return mHistory.size() != 0;
}
public Crumb findCrumb(@NonNull File forDir) {
for (int i = 0; i < mCrumbs.size(); i++) {
if (mCrumbs.get(i).getFile().equals(forDir)) {
return mCrumbs.get(i);
public void restoreFromStateWrapper(SavedStateWrapper mSavedState) {
if (mSavedState != null) {
mActive = mSavedState.mActive;
for (Crumb c : mSavedState.mCrumbs) {
addCrumb(c, false);
}
requestLayout();
setVisibility(mSavedState.mVisibility);
}
}
public void reverseHistory() {
Collections.reverse(mHistory);
}
public void setActivatedContentColor(@ColorInt int contentColorActivated) {
this.contentColorActivated = contentColorActivated;
}
public void setActiveOrAdd(@NonNull Crumb crumb, boolean forceRecreate) {
if (forceRecreate || !setActive(crumb)) {
clearCrumbs();
final List<File> newPathSet = new ArrayList<>();
newPathSet.add(0, crumb.getFile());
File p = crumb.getFile();
while ((p = p.getParentFile()) != null) {
newPathSet.add(0, p);
}
for (int index = 0; index < newPathSet.size(); index++) {
final File fi = newPathSet.get(index);
crumb = new Crumb(fi);
// Restore scroll positions saved before clearing
if (mOldCrumbs != null) {
for (Iterator<Crumb> iterator = mOldCrumbs.iterator(); iterator.hasNext(); ) {
Crumb old = iterator.next();
if (old.equals(crumb)) {
crumb.setScrollPosition(old.getScrollPosition());
iterator.remove(); // minimize number of linear passes by removing un-used crumbs from
// history
break;
}
}
}
return null;
addCrumb(crumb, true);
}
// History no longer needed
mOldCrumbs = null;
}
}
public void setCallback(SelectionCallback callback) {
mCallback = callback;
}
public void setDeactivatedContentColor(@ColorInt int contentColorDeactivated) {
this.contentColorDeactivated = contentColorDeactivated;
}
public int size() {
return mCrumbs.size();
}
public boolean trim(String path, boolean dir) {
if (!dir) {
return false;
}
int index = -1;
for (int i = mCrumbs.size() - 1; i >= 0; i--) {
File fi = mCrumbs.get(i).getFile();
if (fi.getPath().equals(path)) {
index = i;
break;
}
}
public int getActiveIndex() {
return mActive;
boolean removedActive = index >= mActive;
if (index > -1) {
while (index <= mCrumbs.size() - 1) {
removeCrumbAt(index);
}
if (mChildFrame.getChildCount() > 0) {
int lastIndex = mCrumbs.size() - 1;
invalidateActivated(mChildFrame.getChildAt(lastIndex), mActive == lastIndex, false, false);
}
}
return removedActive || mCrumbs.size() == 0;
}
public boolean trim(File file) {
return trim(file.getPath(), file.isDirectory());
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
// RTL works fine like this
View child = mChildFrame.getChildAt(mActive);
if (child != null) {
smoothScrollTo(child.getLeft(), 0);
}
}
void invalidateActivatedAll() {
for (int i = 0; i < mCrumbs.size(); i++) {
Crumb crumb = mCrumbs.get(i);
invalidateActivated(
mChildFrame.getChildAt(i),
mActive == mCrumbs.indexOf(crumb),
false,
i < mCrumbs.size() - 1)
.setText(crumb.getTitle());
}
}
void removeCrumbAt(int index) {
mCrumbs.remove(index);
mChildFrame.removeViewAt(index);
}
void updateIndices() {
for (int i = 0; i < mChildFrame.getChildCount(); i++) {
mChildFrame.getChildAt(i).setTag(i);
}
}
private void init() {
contentColorActivated =
ATHUtil.INSTANCE.resolveColor(getContext(), android.R.attr.textColorPrimary);
contentColorDeactivated =
ATHUtil.INSTANCE.resolveColor(getContext(), android.R.attr.textColorSecondary);
setMinimumHeight((int) getResources().getDimension(R.dimen.tab_height));
setClipToPadding(false);
setHorizontalScrollBarEnabled(false);
mCrumbs = new ArrayList<>();
mHistory = new ArrayList<>();
mChildFrame = new LinearLayout(getContext());
addView(
mChildFrame,
new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
private TextView invalidateActivated(
View view,
final boolean isActive,
final boolean noArrowIfAlone,
final boolean allowArrowVisible) {
int contentColor = isActive ? contentColorActivated : contentColorDeactivated;
LinearLayout child = (LinearLayout) view;
TextView tv = (TextView) child.getChildAt(0);
tv.setTextColor(contentColor);
ImageView iv = (ImageView) child.getChildAt(1);
iv.setColorFilter(contentColor, PorterDuff.Mode.SRC_IN);
if (noArrowIfAlone && getChildCount() == 1) {
iv.setVisibility(View.GONE);
} else if (allowArrowVisible) {
iv.setVisibility(View.VISIBLE);
} else {
iv.setVisibility(View.GONE);
}
return tv;
}
private boolean setActive(Crumb newActive) {
mActive = mCrumbs.indexOf(newActive);
invalidateActivatedAll();
boolean success = mActive > -1;
if (success) {
requestLayout();
}
return success;
}
public interface SelectionCallback {
void onCrumbSelection(Crumb crumb, int index);
}
public static class Crumb implements Parcelable {
public static final Creator<Crumb> CREATOR =
new Creator<Crumb>() {
@Override
public Crumb createFromParcel(Parcel source) {
return new Crumb(source);
}
@Override
public Crumb[] newArray(int size) {
return new Crumb[size];
}
};
private final File file;
private int scrollPos;
public Crumb(File file) {
this.file = file;
}
public Crumb getCrumb(int index) {
return mCrumbs.get(index);
}
public SavedStateWrapper getStateWrapper() {
return new SavedStateWrapper(this);
}
public int historySize() {
return mHistory.size();
}
public Crumb lastHistory() {
if (mHistory.size() == 0) {
return null;
}
return mHistory.get(mHistory.size() - 1);
protected Crumb(Parcel in) {
this.file = (File) in.readSerializable();
this.scrollPos = in.readInt();
}
@Override
public void onClick(View v) {
if (mCallback != null) {
int index = (Integer) v.getTag();
mCallback.onCrumbSelection(mCrumbs.get(index), index);
}
}
public boolean popHistory() {
if (mHistory.size() == 0) {
return false;
}
mHistory.remove(mHistory.size() - 1);
return mHistory.size() != 0;
}
public void restoreFromStateWrapper(SavedStateWrapper mSavedState) {
if (mSavedState != null) {
mActive = mSavedState.mActive;
for (Crumb c : mSavedState.mCrumbs) {
addCrumb(c, false);
}
requestLayout();
setVisibility(mSavedState.mVisibility);
}
}
public void reverseHistory() {
Collections.reverse(mHistory);
}
public void setActivatedContentColor(@ColorInt int contentColorActivated) {
this.contentColorActivated = contentColorActivated;
}
public void setActiveOrAdd(@NonNull Crumb crumb, boolean forceRecreate) {
if (forceRecreate || !setActive(crumb)) {
clearCrumbs();
final List<File> newPathSet = new ArrayList<>();
newPathSet.add(0, crumb.getFile());
File p = crumb.getFile();
while ((p = p.getParentFile()) != null) {
newPathSet.add(0, p);
}
for (int index = 0; index < newPathSet.size(); index++) {
final File fi = newPathSet.get(index);
crumb = new Crumb(fi);
// Restore scroll positions saved before clearing
if (mOldCrumbs != null) {
for (Iterator<Crumb> iterator = mOldCrumbs.iterator(); iterator.hasNext(); ) {
Crumb old = iterator.next();
if (old.equals(crumb)) {
crumb.setScrollPosition(old.getScrollPosition());
iterator.remove(); // minimize number of linear passes by removing un-used crumbs from history
break;
}
}
}
addCrumb(crumb, true);
}
// History no longer needed
mOldCrumbs = null;
}
}
public void setCallback(SelectionCallback callback) {
mCallback = callback;
}
public void setDeactivatedContentColor(@ColorInt int contentColorDeactivated) {
this.contentColorDeactivated = contentColorDeactivated;
}
public int size() {
return mCrumbs.size();
}
public boolean trim(String path, boolean dir) {
if (!dir) {
return false;
}
int index = -1;
for (int i = mCrumbs.size() - 1; i >= 0; i--) {
File fi = mCrumbs.get(i).getFile();
if (fi.getPath().equals(path)) {
index = i;
break;
}
}
boolean removedActive = index >= mActive;
if (index > -1) {
while (index <= mCrumbs.size() - 1) {
removeCrumbAt(index);
}
if (mChildFrame.getChildCount() > 0) {
int lastIndex = mCrumbs.size() - 1;
invalidateActivated(mChildFrame.getChildAt(lastIndex), mActive == lastIndex, false, false);
}
}
return removedActive || mCrumbs.size() == 0;
}
public boolean trim(File file) {
return trim(file.getPath(), file.isDirectory());
public int describeContents() {
return 0;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
//RTL works fine like this
View child = mChildFrame.getChildAt(mActive);
if (child != null) {
smoothScrollTo(child.getLeft(), 0);
}
public boolean equals(Object o) {
return (o instanceof Crumb)
&& ((Crumb) o).getFile() != null
&& ((Crumb) o).getFile().equals(getFile());
}
void invalidateActivatedAll() {
for (int i = 0; i < mCrumbs.size(); i++) {
Crumb crumb = mCrumbs.get(i);
invalidateActivated(mChildFrame.getChildAt(i), mActive == mCrumbs.indexOf(crumb), false,
i < mCrumbs.size() - 1).setText(crumb.getTitle());
}
public File getFile() {
return file;
}
void removeCrumbAt(int index) {
mCrumbs.remove(index);
mChildFrame.removeViewAt(index);
public int getScrollPosition() {
return scrollPos;
}
void updateIndices() {
for (int i = 0; i < mChildFrame.getChildCount(); i++) {
mChildFrame.getChildAt(i).setTag(i);
}
public void setScrollPosition(int scrollY) {
this.scrollPos = scrollY;
}
private void init() {
contentColorActivated = ATHUtil.INSTANCE.resolveColor(getContext(), android.R.attr.textColorPrimary);
contentColorDeactivated = ATHUtil.INSTANCE.resolveColor(getContext(), android.R.attr.textColorSecondary);
setMinimumHeight((int) getResources().getDimension(R.dimen.tab_height));
setClipToPadding(false);
setHorizontalScrollBarEnabled(false);
mCrumbs = new ArrayList<>();
mHistory = new ArrayList<>();
mChildFrame = new LinearLayout(getContext());
addView(mChildFrame, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
public String getTitle() {
return file.getPath().equals("/") ? "root" : file.getName();
}
private TextView invalidateActivated(View view, final boolean isActive, final boolean noArrowIfAlone,
final boolean allowArrowVisible) {
int contentColor = isActive ? contentColorActivated : contentColorDeactivated;
LinearLayout child = (LinearLayout) view;
TextView tv = (TextView) child.getChildAt(0);
tv.setTextColor(contentColor);
ImageView iv = (ImageView) child.getChildAt(1);
iv.setColorFilter(contentColor, PorterDuff.Mode.SRC_IN);
if (noArrowIfAlone && getChildCount() == 1) {
iv.setVisibility(View.GONE);
} else if (allowArrowVisible) {
iv.setVisibility(View.VISIBLE);
} else {
iv.setVisibility(View.GONE);
}
return tv;
@Override
public String toString() {
return "Crumb{" + "file=" + file + ", scrollPos=" + scrollPos + '}';
}
private boolean setActive(Crumb newActive) {
mActive = mCrumbs.indexOf(newActive);
invalidateActivatedAll();
boolean success = mActive > -1;
if (success) {
requestLayout();
}
return success;
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeSerializable(this.file);
dest.writeInt(this.scrollPos);
}
}
public interface SelectionCallback {
public static class SavedStateWrapper implements Parcelable {
void onCrumbSelection(Crumb crumb, int index);
}
public static final Creator<SavedStateWrapper> CREATOR =
new Creator<SavedStateWrapper>() {
public SavedStateWrapper createFromParcel(Parcel source) {
return new SavedStateWrapper(source);
}
public static class Crumb implements Parcelable {
public static final Creator<Crumb> CREATOR = new Creator<Crumb>() {
@Override
public Crumb createFromParcel(Parcel source) {
return new Crumb(source);
}
@Override
public Crumb[] newArray(int size) {
return new Crumb[size];
}
public SavedStateWrapper[] newArray(int size) {
return new SavedStateWrapper[size];
}
};
private final File file;
public final int mActive;
private int scrollPos;
public final List<Crumb> mCrumbs;
public Crumb(File file) {
this.file = file;
}
public final int mVisibility;
protected Crumb(Parcel in) {
this.file = (File) in.readSerializable();
this.scrollPos = in.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public boolean equals(Object o) {
return (o instanceof Crumb) && ((Crumb) o).getFile() != null &&
((Crumb) o).getFile().equals(getFile());
}
public File getFile() {
return file;
}
public int getScrollPosition() {
return scrollPos;
}
public void setScrollPosition(int scrollY) {
this.scrollPos = scrollY;
}
public String getTitle() {
return file.getPath().equals("/") ? "root" : file.getName();
}
@Override
public String toString() {
return "Crumb{" +
"file=" + file +
", scrollPos=" + scrollPos +
'}';
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeSerializable(this.file);
dest.writeInt(this.scrollPos);
}
public SavedStateWrapper(BreadCrumbLayout view) {
mActive = view.mActive;
mCrumbs = view.mCrumbs;
mVisibility = view.getVisibility();
}
public static class SavedStateWrapper implements Parcelable {
public static final Creator<SavedStateWrapper> CREATOR = new Creator<SavedStateWrapper>() {
public SavedStateWrapper createFromParcel(Parcel source) {
return new SavedStateWrapper(source);
}
public SavedStateWrapper[] newArray(int size) {
return new SavedStateWrapper[size];
}
};
public final int mActive;
public final List<Crumb> mCrumbs;
public final int mVisibility;
public SavedStateWrapper(BreadCrumbLayout view) {
mActive = view.mActive;
mCrumbs = view.mCrumbs;
mVisibility = view.getVisibility();
}
protected SavedStateWrapper(Parcel in) {
this.mActive = in.readInt();
this.mCrumbs = in.createTypedArrayList(Crumb.CREATOR);
this.mVisibility = in.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.mActive);
dest.writeTypedList(mCrumbs);
dest.writeInt(this.mVisibility);
}
protected SavedStateWrapper(Parcel in) {
this.mActive = in.readInt();
this.mCrumbs = in.createTypedArrayList(Crumb.CREATOR);
this.mVisibility = in.readInt();
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.mActive);
dest.writeTypedList(mCrumbs);
dest.writeInt(this.mVisibility);
}
}
}

View file

@ -27,299 +27,311 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import androidx.appcompat.widget.AppCompatImageView;
import code.name.monkey.retromusic.R;
public class CircularImageView extends AppCompatImageView {
private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
// Default Values
private static final float DEFAULT_BORDER_WIDTH = 4;
private static final float DEFAULT_SHADOW_RADIUS = 8.0f;
// Default Values
private static final float DEFAULT_BORDER_WIDTH = 4;
private static final float DEFAULT_SHADOW_RADIUS = 8.0f;
// Properties
private float borderWidth;
private int canvasSize;
private float shadowRadius;
private int shadowColor = Color.BLACK;
// Properties
private float borderWidth;
private int canvasSize;
private float shadowRadius;
private int shadowColor = Color.BLACK;
// Object used to draw
private Bitmap image;
private Drawable drawable;
private Paint paint;
private Paint paintBorder;
// Object used to draw
private Bitmap image;
private Drawable drawable;
private Paint paint;
private Paint paintBorder;
//region Constructor & Init Method
public CircularImageView(final Context context) {
this(context, null);
// region Constructor & Init Method
public CircularImageView(final Context context) {
this(context, null);
}
public CircularImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircularImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
// Init paint
paint = new Paint();
paint.setAntiAlias(true);
paintBorder = new Paint();
paintBorder.setAntiAlias(true);
// Load the styled attributes and set their properties
TypedArray attributes =
context.obtainStyledAttributes(attrs, R.styleable.CircularImageView, defStyleAttr, 0);
// Init Border
if (attributes.getBoolean(R.styleable.CircularImageView_civ_border, true)) {
float defaultBorderSize =
DEFAULT_BORDER_WIDTH * getContext().getResources().getDisplayMetrics().density;
setBorderWidth(
attributes.getDimension(
R.styleable.CircularImageView_civ_border_width, defaultBorderSize));
setBorderColor(
attributes.getColor(R.styleable.CircularImageView_civ_border_color, Color.WHITE));
}
public CircularImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
// Init Shadow
if (attributes.getBoolean(R.styleable.CircularImageView_civ_shadow, false)) {
shadowRadius = DEFAULT_SHADOW_RADIUS;
drawShadow(
attributes.getFloat(R.styleable.CircularImageView_civ_shadow_radius, shadowRadius),
attributes.getColor(R.styleable.CircularImageView_civ_shadow_color, shadowColor));
}
attributes.recycle();
}
// endregion
// region Set Attr Method
public void setBorderWidth(float borderWidth) {
this.borderWidth = borderWidth;
requestLayout();
invalidate();
}
public void setBorderColor(int borderColor) {
if (paintBorder != null) {
paintBorder.setColor(borderColor);
}
invalidate();
}
public void addShadow() {
if (shadowRadius == 0) {
shadowRadius = DEFAULT_SHADOW_RADIUS;
}
drawShadow(shadowRadius, shadowColor);
invalidate();
}
public void setShadowRadius(float shadowRadius) {
drawShadow(shadowRadius, shadowColor);
invalidate();
}
public void setShadowColor(int shadowColor) {
drawShadow(shadowRadius, shadowColor);
invalidate();
}
@Override
public ScaleType getScaleType() {
return SCALE_TYPE;
}
@Override
public void setScaleType(ScaleType scaleType) {
if (scaleType != SCALE_TYPE) {
throw new IllegalArgumentException(
String.format(
"ScaleType %s not supported. ScaleType.CENTER_CROP is used by default. So you don't need to use ScaleType.",
scaleType));
}
}
// endregion
// region Draw Method
@Override
public void onDraw(Canvas canvas) {
// Load the bitmap
loadBitmap();
// Check if image isn't null
if (image == null) {
return;
}
public CircularImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
if (!isInEditMode()) {
canvasSize = canvas.getWidth();
if (canvas.getHeight() < canvasSize) {
canvasSize = canvas.getHeight();
}
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
// Init paint
paint = new Paint();
paint.setAntiAlias(true);
// circleCenter is the x or y of the view's center
// radius is the radius in pixels of the cirle to be drawn
// paint contains the shader that will texture the shape
int circleCenter = (int) (canvasSize - (borderWidth * 2)) / 2;
// Draw Border
canvas.drawCircle(
circleCenter + borderWidth,
circleCenter + borderWidth,
circleCenter + borderWidth - (shadowRadius + shadowRadius / 2),
paintBorder);
// Draw CircularImageView
canvas.drawCircle(
circleCenter + borderWidth,
circleCenter + borderWidth,
circleCenter - (shadowRadius + shadowRadius / 2),
paint);
}
paintBorder = new Paint();
paintBorder.setAntiAlias(true);
// Load the styled attributes and set their properties
TypedArray attributes = context
.obtainStyledAttributes(attrs, R.styleable.CircularImageView, defStyleAttr, 0);
// Init Border
if (attributes.getBoolean(R.styleable.CircularImageView_civ_border, true)) {
float defaultBorderSize =
DEFAULT_BORDER_WIDTH * getContext().getResources().getDisplayMetrics().density;
setBorderWidth(attributes
.getDimension(R.styleable.CircularImageView_civ_border_width, defaultBorderSize));
setBorderColor(
attributes.getColor(R.styleable.CircularImageView_civ_border_color, Color.WHITE));
}
// Init Shadow
if (attributes.getBoolean(R.styleable.CircularImageView_civ_shadow, false)) {
shadowRadius = DEFAULT_SHADOW_RADIUS;
drawShadow(attributes.getFloat(R.styleable.CircularImageView_civ_shadow_radius, shadowRadius),
attributes.getColor(R.styleable.CircularImageView_civ_shadow_color, shadowColor));
}
attributes.recycle();
}
//endregion
//region Set Attr Method
public void setBorderWidth(float borderWidth) {
this.borderWidth = borderWidth;
requestLayout();
invalidate();
private void loadBitmap() {
if (this.drawable == getDrawable()) {
return;
}
public void setBorderColor(int borderColor) {
if (paintBorder != null) {
paintBorder.setColor(borderColor);
}
invalidate();
this.drawable = getDrawable();
this.image = drawableToBitmap(this.drawable);
updateShader();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
canvasSize = w;
if (h < canvasSize) {
canvasSize = h;
}
if (image != null) {
updateShader();
}
}
private void drawShadow(float shadowRadius, int shadowColor) {
this.shadowRadius = shadowRadius;
this.shadowColor = shadowColor;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
setLayerType(LAYER_TYPE_SOFTWARE, paintBorder);
}
paintBorder.setShadowLayer(shadowRadius, 0.0f, shadowRadius / 2, shadowColor);
}
private void updateShader() {
if (image == null) {
return;
}
public void addShadow() {
if (shadowRadius == 0) {
shadowRadius = DEFAULT_SHADOW_RADIUS;
}
drawShadow(shadowRadius, shadowColor);
invalidate();
// Crop Center Image
image = cropBitmap(image);
// Create Shader
BitmapShader shader = new BitmapShader(image, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
// Center Image in Shader
Matrix matrix = new Matrix();
matrix.setScale(
(float) canvasSize / (float) image.getWidth(),
(float) canvasSize / (float) image.getHeight());
shader.setLocalMatrix(matrix);
// Set Shader in Paint
paint.setShader(shader);
}
private Bitmap cropBitmap(Bitmap bitmap) {
Bitmap bmp;
if (bitmap.getWidth() >= bitmap.getHeight()) {
bmp =
Bitmap.createBitmap(
bitmap,
bitmap.getWidth() / 2 - bitmap.getHeight() / 2,
0,
bitmap.getHeight(),
bitmap.getHeight());
} else {
bmp =
Bitmap.createBitmap(
bitmap,
0,
bitmap.getHeight() / 2 - bitmap.getWidth() / 2,
bitmap.getWidth(),
bitmap.getWidth());
}
return bmp;
}
private Bitmap drawableToBitmap(Drawable drawable) {
if (drawable == null) {
return null;
} else if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
public void setShadowRadius(float shadowRadius) {
drawShadow(shadowRadius, shadowColor);
invalidate();
int intrinsicWidth = drawable.getIntrinsicWidth();
int intrinsicHeight = drawable.getIntrinsicHeight();
if (!(intrinsicWidth > 0 && intrinsicHeight > 0)) {
return null;
}
public void setShadowColor(int shadowColor) {
drawShadow(shadowRadius, shadowColor);
invalidate();
try {
// Create Bitmap object out of the drawable
Bitmap bitmap = Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
} catch (OutOfMemoryError e) {
// Simply return null of failed bitmap creations
Log.e(getClass().toString(), "Encountered OutOfMemoryError while generating bitmap!");
return null;
}
}
// endregion
// region Mesure Method
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = measureWidth(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec);
/*int imageSize = (width < height) ? width : height;
setMeasuredDimension(imageSize, imageSize);*/
setMeasuredDimension(width, height);
}
private int measureWidth(int measureSpec) {
int result;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// The parent has determined an exact size for the child.
result = specSize;
} else if (specMode == MeasureSpec.AT_MOST) {
// The child can be as large as it wants up to the specified size.
result = specSize;
} else {
// The parent has not imposed any constraint on the child.
result = canvasSize;
}
@Override
public ScaleType getScaleType() {
return SCALE_TYPE;
return result;
}
private int measureHeight(int measureSpecHeight) {
int result;
int specMode = MeasureSpec.getMode(measureSpecHeight);
int specSize = MeasureSpec.getSize(measureSpecHeight);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else if (specMode == MeasureSpec.AT_MOST) {
// The child can be as large as it wants up to the specified size.
result = specSize;
} else {
// Measure the text (beware: ascent is a negative number)
result = canvasSize;
}
@Override
public void setScaleType(ScaleType scaleType) {
if (scaleType != SCALE_TYPE) {
throw new IllegalArgumentException(String.format(
"ScaleType %s not supported. ScaleType.CENTER_CROP is used by default. So you don't need to use ScaleType.",
scaleType));
}
}
//endregion
//region Draw Method
@Override
public void onDraw(Canvas canvas) {
// Load the bitmap
loadBitmap();
// Check if image isn't null
if (image == null) {
return;
}
if (!isInEditMode()) {
canvasSize = canvas.getWidth();
if (canvas.getHeight() < canvasSize) {
canvasSize = canvas.getHeight();
}
}
// circleCenter is the x or y of the view's center
// radius is the radius in pixels of the cirle to be drawn
// paint contains the shader that will texture the shape
int circleCenter = (int) (canvasSize - (borderWidth * 2)) / 2;
// Draw Border
canvas.drawCircle(circleCenter + borderWidth, circleCenter + borderWidth,
circleCenter + borderWidth - (shadowRadius + shadowRadius / 2), paintBorder);
// Draw CircularImageView
canvas.drawCircle(circleCenter + borderWidth, circleCenter + borderWidth,
circleCenter - (shadowRadius + shadowRadius / 2), paint);
}
private void loadBitmap() {
if (this.drawable == getDrawable()) {
return;
}
this.drawable = getDrawable();
this.image = drawableToBitmap(this.drawable);
updateShader();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
canvasSize = w;
if (h < canvasSize) {
canvasSize = h;
}
if (image != null) {
updateShader();
}
}
private void drawShadow(float shadowRadius, int shadowColor) {
this.shadowRadius = shadowRadius;
this.shadowColor = shadowColor;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
setLayerType(LAYER_TYPE_SOFTWARE, paintBorder);
}
paintBorder.setShadowLayer(shadowRadius, 0.0f, shadowRadius / 2, shadowColor);
}
private void updateShader() {
if (image == null) {
return;
}
// Crop Center Image
image = cropBitmap(image);
// Create Shader
BitmapShader shader = new BitmapShader(image, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
// Center Image in Shader
Matrix matrix = new Matrix();
matrix.setScale((float) canvasSize / (float) image.getWidth(),
(float) canvasSize / (float) image.getHeight());
shader.setLocalMatrix(matrix);
// Set Shader in Paint
paint.setShader(shader);
}
private Bitmap cropBitmap(Bitmap bitmap) {
Bitmap bmp;
if (bitmap.getWidth() >= bitmap.getHeight()) {
bmp = Bitmap.createBitmap(
bitmap,
bitmap.getWidth() / 2 - bitmap.getHeight() / 2,
0,
bitmap.getHeight(), bitmap.getHeight());
} else {
bmp = Bitmap.createBitmap(
bitmap,
0,
bitmap.getHeight() / 2 - bitmap.getWidth() / 2,
bitmap.getWidth(), bitmap.getWidth());
}
return bmp;
}
private Bitmap drawableToBitmap(Drawable drawable) {
if (drawable == null) {
return null;
} else if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
int intrinsicWidth = drawable.getIntrinsicWidth();
int intrinsicHeight = drawable.getIntrinsicHeight();
if (!(intrinsicWidth > 0 && intrinsicHeight > 0)) {
return null;
}
try {
// Create Bitmap object out of the drawable
Bitmap bitmap = Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
} catch (OutOfMemoryError e) {
// Simply return null of failed bitmap creations
Log.e(getClass().toString(), "Encountered OutOfMemoryError while generating bitmap!");
return null;
}
}
//endregion
//region Mesure Method
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = measureWidth(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec);
/*int imageSize = (width < height) ? width : height;
setMeasuredDimension(imageSize, imageSize);*/
setMeasuredDimension(width, height);
}
private int measureWidth(int measureSpec) {
int result;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// The parent has determined an exact size for the child.
result = specSize;
} else if (specMode == MeasureSpec.AT_MOST) {
// The child can be as large as it wants up to the specified size.
result = specSize;
} else {
// The parent has not imposed any constraint on the child.
result = canvasSize;
}
return result;
}
private int measureHeight(int measureSpecHeight) {
int result;
int specMode = MeasureSpec.getMode(measureSpecHeight);
int specSize = MeasureSpec.getSize(measureSpecHeight);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else if (specMode == MeasureSpec.AT_MOST) {
// The child can be as large as it wants up to the specified size.
result = specSize;
} else {
// Measure the text (beware: ascent is a negative number)
result = canvasSize;
}
return (result + 2);
}
//endregion
}
return (result + 2);
}
// endregion
}

View file

@ -14,6 +14,8 @@
package code.name.monkey.retromusic.views;
import static code.name.monkey.retromusic.util.RetroUtil.openUrl;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
@ -22,55 +24,54 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import code.name.monkey.retromusic.R;
import static code.name.monkey.retromusic.util.RetroUtil.openUrl;
public class ContributorsView extends FrameLayout {
public ContributorsView(@NonNull Context context) {
super(context);
init(context, null);
}
public ContributorsView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public ContributorsView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attributeSet) {
final TypedArray attributes = context.obtainStyledAttributes(attributeSet, R.styleable.ContributorsView, 0, 0);
if (attributes != null) {
final View layout = LayoutInflater.from(context).inflate(R.layout.item_contributor, this);
NetworkImageView networkImageView = layout.findViewById(R.id.image);
String url = attributes.getString(R.styleable.ContributorsView_profile_url);
networkImageView.setImageUrl(url);
String name = attributes.getString(R.styleable.ContributorsView_profile_name);
TextView title = layout.findViewById(R.id.title);
title.setText(name);
String summary = attributes.getString(R.styleable.ContributorsView_profile_summary);
TextView text = layout.findViewById(R.id.text);
text.setText(summary);
String link = attributes.getString(R.styleable.ContributorsView_profile_link);
layout.setOnClickListener(v -> {
if (link == null) {
return;
}
openUrl((Activity) getContext(), link);
});
attributes.recycle();
}
public ContributorsView(@NonNull Context context) {
super(context);
init(context, null);
}
public ContributorsView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public ContributorsView(
@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attributeSet) {
final TypedArray attributes =
context.obtainStyledAttributes(attributeSet, R.styleable.ContributorsView, 0, 0);
if (attributes != null) {
final View layout = LayoutInflater.from(context).inflate(R.layout.item_contributor, this);
NetworkImageView networkImageView = layout.findViewById(R.id.image);
String url = attributes.getString(R.styleable.ContributorsView_profile_url);
networkImageView.setImageUrl(url);
String name = attributes.getString(R.styleable.ContributorsView_profile_name);
TextView title = layout.findViewById(R.id.title);
title.setText(name);
String summary = attributes.getString(R.styleable.ContributorsView_profile_summary);
TextView text = layout.findViewById(R.id.text);
text.setText(summary);
String link = attributes.getString(R.styleable.ContributorsView_profile_link);
layout.setOnClickListener(
v -> {
if (link == null) {
return;
}
openUrl((Activity) getContext(), link);
});
attributes.recycle();
}
}
}

View file

@ -17,20 +17,19 @@ package code.name.monkey.retromusic.views;
import android.graphics.drawable.GradientDrawable;
public class DrawableGradient extends GradientDrawable {
public DrawableGradient(Orientation orientations, int[] colors, int shape) {
super(orientations, colors);
try {
setShape(shape);
setGradientType(GradientDrawable.LINEAR_GRADIENT);
setCornerRadius(0);
} catch (Exception e) {
e.printStackTrace();
}
}
public DrawableGradient SetTransparency(int transparencyPercent) {
this.setAlpha(255 - ((255 * transparencyPercent) / 100));
return this;
public DrawableGradient(Orientation orientations, int[] colors, int shape) {
super(orientations, colors);
try {
setShape(shape);
setGradientType(GradientDrawable.LINEAR_GRADIENT);
setCornerRadius(0);
} catch (Exception e) {
e.printStackTrace();
}
}
public DrawableGradient SetTransparency(int transparencyPercent) {
this.setAlpha(255 - ((255 * transparencyPercent) / 100));
return this;
}
}

View file

@ -20,34 +20,34 @@ import android.util.AttributeSet;
import android.widget.FrameLayout;
public class HeightFitSquareLayout extends FrameLayout {
private boolean forceSquare = true;
private boolean forceSquare = true;
public HeightFitSquareLayout(Context context) {
super(context);
}
public HeightFitSquareLayout(Context context) {
super(context);
}
public HeightFitSquareLayout(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
}
public HeightFitSquareLayout(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
}
public HeightFitSquareLayout(Context context, AttributeSet attributeSet, int i) {
super(context, attributeSet, i);
}
public HeightFitSquareLayout(Context context, AttributeSet attributeSet, int i) {
super(context, attributeSet, i);
}
@TargetApi(21)
public HeightFitSquareLayout(Context context, AttributeSet attributeSet, int i, int i2) {
super(context, attributeSet, i, i2);
}
@TargetApi(21)
public HeightFitSquareLayout(Context context, AttributeSet attributeSet, int i, int i2) {
super(context, attributeSet, i, i2);
}
public void forceSquare(boolean z) {
this.forceSquare = z;
requestLayout();
}
public void forceSquare(boolean z) {
this.forceSquare = z;
requestLayout();
}
protected void onMeasure(int i, int i2) {
if (this.forceSquare) {
i = i2;
}
super.onMeasure(i, i2);
protected void onMeasure(int i, int i2) {
if (this.forceSquare) {
i = i2;
}
super.onMeasure(i, i2);
}
}

View file

@ -22,28 +22,30 @@ import android.util.AttributeSet;
import android.webkit.WebView;
public class LollipopFixedWebView extends WebView {
public LollipopFixedWebView(Context context) {
super(getFixedContext(context));
}
public LollipopFixedWebView(Context context) {
super(getFixedContext(context));
}
public LollipopFixedWebView(Context context, AttributeSet attrs) {
super(getFixedContext(context), attrs);
}
public LollipopFixedWebView(Context context, AttributeSet attrs) {
super(getFixedContext(context), attrs);
}
public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(getFixedContext(context), attrs, defStyleAttr);
}
public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(getFixedContext(context), attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(getFixedContext(context), attrs, defStyleAttr, defStyleRes);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public LollipopFixedWebView(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(getFixedContext(context), attrs, defStyleAttr, defStyleRes);
}
public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr, boolean privateBrowsing) {
super(getFixedContext(context), attrs, defStyleAttr, privateBrowsing);
}
public LollipopFixedWebView(
Context context, AttributeSet attrs, int defStyleAttr, boolean privateBrowsing) {
super(getFixedContext(context), attrs, defStyleAttr, privateBrowsing);
}
public static Context getFixedContext(Context context) {
return context.createConfigurationContext(new Configuration());
}
}
public static Context getFixedContext(Context context) {
return context.createConfigurationContext(new Configuration());
}
}

View file

@ -17,50 +17,47 @@ package code.name.monkey.retromusic.views;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import code.name.monkey.retromusic.R;
import com.bumptech.glide.Glide;
import code.name.monkey.retromusic.R;
/**
* @author Hemanth S (h4h13).
*/
/** @author Hemanth S (h4h13). */
public class NetworkImageView extends CircularImageView {
public NetworkImageView(@NonNull Context context) {
super(context);
init(context, null);
}
public NetworkImageView(@NonNull Context context) {
super(context);
init(context, null);
}
public NetworkImageView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public NetworkImageView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public NetworkImageView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
public NetworkImageView(
@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
public void setImageUrl(@NonNull String imageUrl) {
setImageUrl(getContext(), imageUrl);
}
public void setImageUrl(@NonNull String imageUrl) {
setImageUrl(getContext(), imageUrl);
}
public void setImageUrl(@NonNull Context context, @NonNull String imageUrl) {
Glide.with(context)
.load(imageUrl)
.error(R.drawable.ic_account)
.placeholder(R.drawable.ic_account)
.into(this);
}
public void setImageUrl(@NonNull Context context, @NonNull String imageUrl) {
Glide.with(context)
.load(imageUrl)
.error(R.drawable.ic_account)
.placeholder(R.drawable.ic_account)
.into(this);
}
private void init(Context context, AttributeSet attributeSet) {
TypedArray attributes = context.obtainStyledAttributes(attributeSet, R.styleable.NetworkImageView, 0, 0);
String url = attributes.getString(R.styleable.NetworkImageView_url_link);
setImageUrl(context, url);
attributes.recycle();
}
private void init(Context context, AttributeSet attributeSet) {
TypedArray attributes =
context.obtainStyledAttributes(attributeSet, R.styleable.NetworkImageView, 0, 0);
String url = attributes.getString(R.styleable.NetworkImageView_url_link);
setImageUrl(context, url);
attributes.recycle();
}
}

View file

@ -27,134 +27,135 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.DrawableCompat;
import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.retromusic.R;
public class PopupBackground extends Drawable {
private final int mPaddingEnd;
private final int mPaddingEnd;
private final int mPaddingStart;
private final int mPaddingStart;
@NonNull
private final Paint mPaint;
@NonNull private final Paint mPaint;
@NonNull
private final Path mPath = new Path();
@NonNull private final Path mPath = new Path();
@NonNull
private final Matrix mTempMatrix = new Matrix();
@NonNull private final Matrix mTempMatrix = new Matrix();
public PopupBackground(@NonNull Context context) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(ThemeStore.Companion.accentColor(context));
mPaint.setStyle(Paint.Style.FILL);
Resources resources = context.getResources();
mPaddingStart = resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_padding_start);
mPaddingEnd = resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_padding_end);
public PopupBackground(@NonNull Context context) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(ThemeStore.Companion.accentColor(context));
mPaint.setStyle(Paint.Style.FILL);
Resources resources = context.getResources();
mPaddingStart = resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_padding_start);
mPaddingEnd = resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_padding_end);
}
private static void pathArcTo(
@NonNull Path path,
float centerX,
float centerY,
float radius,
float startAngle,
float sweepAngle) {
path.arcTo(
centerX - radius,
centerY - radius,
centerX + radius,
centerY + radius,
startAngle,
sweepAngle,
false);
}
@Override
public void draw(@NonNull Canvas canvas) {
canvas.drawPath(mPath, mPaint);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void getOutline(@NonNull Outline outline) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && !mPath.isConvex()) {
// The outline path must be convex before Q, but we may run into floating point error
// caused by calculation involving sqrt(2) or OEM implementation difference, so in this
// case we just omit the shadow instead of crashing.
super.getOutline(outline);
return;
}
outline.setConvexPath(mPath);
}
private static void pathArcTo(@NonNull Path path, float centerX, float centerY, float radius,
float startAngle, float sweepAngle) {
path.arcTo(centerX - radius, centerY - radius, centerX + radius, centerY + radius,
startAngle, sweepAngle, false);
@Override
public void setAlpha(int alpha) {}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {}
@Override
public boolean getPadding(@NonNull Rect padding) {
if (needMirroring()) {
padding.set(mPaddingEnd, 0, mPaddingStart, 0);
} else {
padding.set(mPaddingStart, 0, mPaddingEnd, 0);
}
return true;
}
@Override
public void draw(@NonNull Canvas canvas) {
canvas.drawPath(mPath, mPaint);
@Override
public boolean isAutoMirrored() {
return true;
}
@Override
public boolean onLayoutDirectionChanged(int layoutDirection) {
updatePath();
return true;
}
@Override
protected void onBoundsChange(@NonNull Rect bounds) {
updatePath();
}
private boolean needMirroring() {
return DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL;
}
private void updatePath() {
mPath.reset();
Rect bounds = getBounds();
float width = bounds.width();
float height = bounds.height();
float r = height / 2;
float sqrt2 = (float) Math.sqrt(2);
// Ensure we are convex.
width = Math.max(r + sqrt2 * r, width);
pathArcTo(mPath, r, r, r, 90, 180);
float o1X = width - sqrt2 * r;
pathArcTo(mPath, o1X, r, r, -90, 45f);
float r2 = r / 5;
float o2X = width - sqrt2 * r2;
pathArcTo(mPath, o2X, r, r2, -45, 90);
pathArcTo(mPath, o1X, r, r, 45f, 45f);
mPath.close();
if (needMirroring()) {
mTempMatrix.setScale(-1, 1, width / 2, 0);
} else {
mTempMatrix.reset();
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void getOutline(@NonNull Outline outline) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && !mPath.isConvex()) {
// The outline path must be convex before Q, but we may run into floating point error
// caused by calculation involving sqrt(2) or OEM implementation difference, so in this
// case we just omit the shadow instead of crashing.
super.getOutline(outline);
return;
}
outline.setConvexPath(mPath);
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
}
@Override
public boolean getPadding(@NonNull Rect padding) {
if (needMirroring()) {
padding.set(mPaddingEnd, 0, mPaddingStart, 0);
} else {
padding.set(mPaddingStart, 0, mPaddingEnd, 0);
}
return true;
}
@Override
public boolean isAutoMirrored() {
return true;
}
@Override
public boolean onLayoutDirectionChanged(int layoutDirection) {
updatePath();
return true;
}
@Override
protected void onBoundsChange(@NonNull Rect bounds) {
updatePath();
}
private boolean needMirroring() {
return DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL;
}
private void updatePath() {
mPath.reset();
Rect bounds = getBounds();
float width = bounds.width();
float height = bounds.height();
float r = height / 2;
float sqrt2 = (float) Math.sqrt(2);
// Ensure we are convex.
width = Math.max(r + sqrt2 * r, width);
pathArcTo(mPath, r, r, r, 90, 180);
float o1X = width - sqrt2 * r;
pathArcTo(mPath, o1X, r, r, -90, 45f);
float r2 = r / 5;
float o2X = width - sqrt2 * r2;
pathArcTo(mPath, o2X, r, r2, -45, 90);
pathArcTo(mPath, o1X, r, r, 45f, 45f);
mPath.close();
if (needMirroring()) {
mTempMatrix.setScale(-1, 1, width / 2, 0);
} else {
mTempMatrix.reset();
}
mTempMatrix.postTranslate(bounds.left, bounds.top);
mPath.transform(mTempMatrix);
}
}
mTempMatrix.postTranslate(bounds.left, bounds.top);
mPath.transform(mTempMatrix);
}
}

View file

@ -17,42 +17,46 @@ package code.name.monkey.retromusic.views;
import android.graphics.Rect;
import android.view.View;
import android.view.WindowInsets;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import me.zhanghai.android.fastscroll.FastScroller;
public class ScrollingViewOnApplyWindowInsetsListener implements View.OnApplyWindowInsetsListener {
@NonNull
private final Rect mPadding = new Rect();
@Nullable
private final FastScroller mFastScroller;
@NonNull private final Rect mPadding = new Rect();
@Nullable private final FastScroller mFastScroller;
public ScrollingViewOnApplyWindowInsetsListener(@Nullable View view,
@Nullable FastScroller fastScroller) {
if (view != null) {
mPadding.set(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(),
view.getPaddingBottom());
}
mFastScroller = fastScroller;
public ScrollingViewOnApplyWindowInsetsListener(
@Nullable View view, @Nullable FastScroller fastScroller) {
if (view != null) {
mPadding.set(
view.getPaddingLeft(),
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
mFastScroller = fastScroller;
}
public ScrollingViewOnApplyWindowInsetsListener() {
this(null, null);
}
public ScrollingViewOnApplyWindowInsetsListener() {
this(null, null);
}
@NonNull
@Override
public WindowInsets onApplyWindowInsets(@NonNull View view, @NonNull WindowInsets insets) {
view.setPadding(mPadding.left + insets.getSystemWindowInsetLeft(), mPadding.top,
mPadding.right + insets.getSystemWindowInsetRight(),
mPadding.bottom + insets.getSystemWindowInsetBottom());
if (mFastScroller != null) {
mFastScroller.setPadding(insets.getSystemWindowInsetLeft(), 0,
insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
}
return insets;
@NonNull
@Override
public WindowInsets onApplyWindowInsets(@NonNull View view, @NonNull WindowInsets insets) {
view.setPadding(
mPadding.left + insets.getSystemWindowInsetLeft(),
mPadding.top,
mPadding.right + insets.getSystemWindowInsetRight(),
mPadding.bottom + insets.getSystemWindowInsetBottom());
if (mFastScroller != null) {
mFastScroller.setPadding(
insets.getSystemWindowInsetLeft(),
0,
insets.getSystemWindowInsetRight(),
insets.getSystemWindowInsetBottom());
}
}
return insets;
}
}

View file

@ -24,529 +24,488 @@ import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import code.name.monkey.retromusic.R;
/**
* SeekArc.java
* <p>
* This is a class that functions much like a SeekBar but
* follows a circle path instead of a straight line.
*
* <p>This is a class that functions much like a SeekBar but follows a circle path instead of a
* straight line.
*
* @author Neil Davies
*/
public class SeekArc extends View {
private static final String TAG = SeekArc.class.getSimpleName();
private static int INVALID_PROGRESS_VALUE = -1;
// The initial rotational offset -90 means we start at 12 o'clock
private final int mAngleOffset = -90;
private Paint mArcPaint;
// Internal variables
private int mArcRadius = 0;
private RectF mArcRect = new RectF();
/**
* The Width of the background arc for the SeekArc
*/
private int mArcWidth = 2;
/**
* Will the progress increase clockwise or anti-clockwise
*/
private boolean mClockwise = true;
/**
* is the control enabled/touchable
*/
private boolean mEnabled = true;
/**
* The Maximum value that this SeekArc can be set to
*/
private int mMax = 100;
private OnSeekArcChangeListener mOnSeekArcChangeListener;
/**
* The Current value that the SeekArc is set to
*/
private int mProgress = 0;
private Paint mProgressPaint;
private float mProgressSweep = 0;
/**
* The width of the progress line for this SeekArc
*/
private int mProgressWidth = 4;
/**
* The rotation of the SeekArc- 0 is twelve o'clock
*/
private int mRotation = 0;
/**
* Give the SeekArc rounded edges
*/
private boolean mRoundedEdges = false;
/**
* The Angle to start drawing this Arc from
*/
private int mStartAngle = 0;
/**
* The Angle through which to draw the arc (Max is 360)
*/
private int mSweepAngle = 360;
/**
* The Drawable for the seek arc thumbnail
*/
private Drawable mThumb;
private int mThumbXPos;
private int mThumbYPos;
private double mTouchAngle;
private float mTouchIgnoreRadius;
/**
* Enable touch inside the SeekArc
*/
private boolean mTouchInside = true;
private int mTranslateX;
private int mTranslateY;
private static final String TAG = SeekArc.class.getSimpleName();
private static int INVALID_PROGRESS_VALUE = -1;
// The initial rotational offset -90 means we start at 12 o'clock
private final int mAngleOffset = -90;
private Paint mArcPaint;
// Internal variables
private int mArcRadius = 0;
private RectF mArcRect = new RectF();
/** The Width of the background arc for the SeekArc */
private int mArcWidth = 2;
/** Will the progress increase clockwise or anti-clockwise */
private boolean mClockwise = true;
/** is the control enabled/touchable */
private boolean mEnabled = true;
/** The Maximum value that this SeekArc can be set to */
private int mMax = 100;
public SeekArc(Context context) {
super(context);
init(context, null, 0);
private OnSeekArcChangeListener mOnSeekArcChangeListener;
/** The Current value that the SeekArc is set to */
private int mProgress = 0;
private Paint mProgressPaint;
private float mProgressSweep = 0;
/** The width of the progress line for this SeekArc */
private int mProgressWidth = 4;
/** The rotation of the SeekArc- 0 is twelve o'clock */
private int mRotation = 0;
/** Give the SeekArc rounded edges */
private boolean mRoundedEdges = false;
/** The Angle to start drawing this Arc from */
private int mStartAngle = 0;
/** The Angle through which to draw the arc (Max is 360) */
private int mSweepAngle = 360;
/** The Drawable for the seek arc thumbnail */
private Drawable mThumb;
private int mThumbXPos;
private int mThumbYPos;
private double mTouchAngle;
private float mTouchIgnoreRadius;
/** Enable touch inside the SeekArc */
private boolean mTouchInside = true;
private int mTranslateX;
private int mTranslateY;
public SeekArc(Context context) {
super(context);
init(context, null, 0);
}
public SeekArc(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, R.attr.seekArcStyle);
}
public SeekArc(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
}
public int getArcColor() {
return mArcPaint.getColor();
}
public void setArcColor(int color) {
mArcPaint.setColor(color);
invalidate();
}
public int getArcRotation() {
return mRotation;
}
public void setArcRotation(int mRotation) {
this.mRotation = mRotation;
updateThumbPosition();
}
public int getArcWidth() {
return mArcWidth;
}
public void setArcWidth(int mArcWidth) {
this.mArcWidth = mArcWidth;
mArcPaint.setStrokeWidth(mArcWidth);
}
public int getMax() {
return mMax;
}
public void setMax(int mMax) {
this.mMax = mMax;
}
public int getProgress() {
return mProgress;
}
public void setProgress(int progress) {
updateProgress(progress, false);
}
public int getProgressColor() {
return mProgressPaint.getColor();
}
public void setProgressColor(int color) {
mProgressPaint.setColor(color);
invalidate();
}
public int getProgressWidth() {
return mProgressWidth;
}
public void setProgressWidth(int mProgressWidth) {
this.mProgressWidth = mProgressWidth;
mProgressPaint.setStrokeWidth(mProgressWidth);
}
public int getStartAngle() {
return mStartAngle;
}
public void setStartAngle(int mStartAngle) {
this.mStartAngle = mStartAngle;
updateThumbPosition();
}
public int getSweepAngle() {
return mSweepAngle;
}
public void setSweepAngle(int mSweepAngle) {
this.mSweepAngle = mSweepAngle;
updateThumbPosition();
}
public boolean isClockwise() {
return mClockwise;
}
public void setClockwise(boolean isClockwise) {
mClockwise = isClockwise;
}
public boolean isEnabled() {
return mEnabled;
}
public void setEnabled(boolean enabled) {
this.mEnabled = enabled;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mEnabled) {
this.getParent().requestDisallowInterceptTouchEvent(true);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
onStartTrackingTouch();
updateOnTouch(event);
break;
case MotionEvent.ACTION_MOVE:
updateOnTouch(event);
break;
case MotionEvent.ACTION_UP:
onStopTrackingTouch();
setPressed(false);
this.getParent().requestDisallowInterceptTouchEvent(false);
break;
case MotionEvent.ACTION_CANCEL:
onStopTrackingTouch();
setPressed(false);
this.getParent().requestDisallowInterceptTouchEvent(false);
break;
}
return true;
}
return false;
}
/**
* Sets a listener to receive notifications of changes to the SeekArc's progress level. Also
* provides notifications of when the user starts and stops a touch gesture within the SeekArc.
*
* @param l The seek bar notification listener
* @see SeekArc.OnSeekBarChangeListener
*/
public void setOnSeekArcChangeListener(OnSeekArcChangeListener l) {
mOnSeekArcChangeListener = l;
}
public void setRoundedEdges(boolean isEnabled) {
mRoundedEdges = isEnabled;
if (mRoundedEdges) {
mArcPaint.setStrokeCap(Paint.Cap.ROUND);
mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
} else {
mArcPaint.setStrokeCap(Paint.Cap.SQUARE);
mProgressPaint.setStrokeCap(Paint.Cap.SQUARE);
}
}
public void setTouchInSide(boolean isEnabled) {
int thumbHalfheight = (int) mThumb.getIntrinsicHeight() / 2;
int thumbHalfWidth = (int) mThumb.getIntrinsicWidth() / 2;
mTouchInside = isEnabled;
if (mTouchInside) {
mTouchIgnoreRadius = (float) mArcRadius / 4;
} else {
// Don't use the exact radius makes interaction too tricky
mTouchIgnoreRadius = mArcRadius - Math.min(thumbHalfWidth, thumbHalfheight);
}
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mThumb != null && mThumb.isStateful()) {
int[] state = getDrawableState();
mThumb.setState(state);
}
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
if (!mClockwise) {
canvas.scale(-1, 1, mArcRect.centerX(), mArcRect.centerY());
}
public SeekArc(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, R.attr.seekArcStyle);
// Draw the arcs
final int arcStart = mStartAngle + mAngleOffset + mRotation;
final int arcSweep = mSweepAngle;
canvas.drawArc(mArcRect, arcStart, arcSweep, false, mArcPaint);
canvas.drawArc(mArcRect, arcStart, mProgressSweep, false, mProgressPaint);
if (mEnabled) {
// Draw the thumb nail
canvas.translate(mTranslateX - mThumbXPos, mTranslateY - mThumbYPos);
mThumb.draw(canvas);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
final int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int min = Math.min(width, height);
float top = 0;
float left = 0;
int arcDiameter = 0;
mTranslateX = (int) (width * 0.5f);
mTranslateY = (int) (height * 0.5f);
arcDiameter = min - getPaddingLeft();
mArcRadius = arcDiameter / 2;
top = height / 2 - (arcDiameter / 2);
left = width / 2 - (arcDiameter / 2);
mArcRect.set(left, top, left + arcDiameter, top + arcDiameter);
int arcStart = (int) mProgressSweep + mStartAngle + mRotation + 90;
mThumbXPos = (int) (mArcRadius * Math.cos(Math.toRadians(arcStart)));
mThumbYPos = (int) (mArcRadius * Math.sin(Math.toRadians(arcStart)));
setTouchInSide(mTouchInside);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private int getProgressForAngle(double angle) {
int touchProgress = (int) Math.round(valuePerDegree() * angle);
touchProgress = (touchProgress < 0) ? INVALID_PROGRESS_VALUE : touchProgress;
touchProgress = (touchProgress > mMax) ? INVALID_PROGRESS_VALUE : touchProgress;
return touchProgress;
}
private double getTouchDegrees(float xPos, float yPos) {
float x = xPos - mTranslateX;
float y = yPos - mTranslateY;
// invert the x-coord if we are rotating anti-clockwise
x = (mClockwise) ? x : -x;
// convert to arc Angle
double angle = Math.toDegrees(Math.atan2(y, x) + (Math.PI / 2) - Math.toRadians(mRotation));
if (angle < 0) {
angle = 360 + angle;
}
angle -= mStartAngle;
return angle;
}
private boolean ignoreTouch(float xPos, float yPos) {
boolean ignore = false;
float x = xPos - mTranslateX;
float y = yPos - mTranslateY;
float touchRadius = (float) Math.sqrt(((x * x) + (y * y)));
if (touchRadius < mTouchIgnoreRadius) {
ignore = true;
}
return ignore;
}
private void init(Context context, AttributeSet attrs, int defStyle) {
Log.d(TAG, "Initialising SeekArc");
final Resources res = getResources();
float density = context.getResources().getDisplayMetrics().density;
// Defaults, may need to link this into theme settings
int arcColor = res.getColor(R.color.progress_gray);
int progressColor = res.getColor(R.color.default_blue_light);
int thumbHalfheight = 0;
int thumbHalfWidth = 0;
mThumb = res.getDrawable(R.drawable.switch_thumb_material);
// Convert progress width to pixels for current density
mProgressWidth = (int) (mProgressWidth * density);
if (attrs != null) {
// Attribute initialization
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SeekArc, defStyle, 0);
Drawable thumb = a.getDrawable(R.styleable.SeekArc_thumb);
if (thumb != null) {
mThumb = thumb;
}
thumbHalfheight = (int) mThumb.getIntrinsicHeight() / 2;
thumbHalfWidth = (int) mThumb.getIntrinsicWidth() / 2;
mThumb.setBounds(-thumbHalfWidth, -thumbHalfheight, thumbHalfWidth, thumbHalfheight);
mMax = a.getInteger(R.styleable.SeekArc_max, mMax);
mProgress = a.getInteger(R.styleable.SeekArc_seekProgress, mProgress);
mProgressWidth = (int) a.getDimension(R.styleable.SeekArc_progressWidth, mProgressWidth);
mArcWidth = (int) a.getDimension(R.styleable.SeekArc_arcWidth, mArcWidth);
mStartAngle = a.getInt(R.styleable.SeekArc_startAngle, mStartAngle);
mSweepAngle = a.getInt(R.styleable.SeekArc_sweepAngle, mSweepAngle);
mRotation = a.getInt(R.styleable.SeekArc_rotation, mRotation);
mRoundedEdges = a.getBoolean(R.styleable.SeekArc_roundEdges, mRoundedEdges);
mTouchInside = a.getBoolean(R.styleable.SeekArc_touchInside, mTouchInside);
mClockwise = a.getBoolean(R.styleable.SeekArc_clockwise, mClockwise);
mEnabled = a.getBoolean(R.styleable.SeekArc_enabled, mEnabled);
arcColor = a.getColor(R.styleable.SeekArc_arcColor, arcColor);
progressColor = a.getColor(R.styleable.SeekArc_progressColor, progressColor);
a.recycle();
}
public SeekArc(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
mProgress = (mProgress > mMax) ? mMax : mProgress;
mProgress = (mProgress < 0) ? 0 : mProgress;
mSweepAngle = (mSweepAngle > 360) ? 360 : mSweepAngle;
mSweepAngle = (mSweepAngle < 0) ? 0 : mSweepAngle;
mProgressSweep = (float) mProgress / mMax * mSweepAngle;
mStartAngle = (mStartAngle > 360) ? 0 : mStartAngle;
mStartAngle = (mStartAngle < 0) ? 0 : mStartAngle;
mArcPaint = new Paint();
mArcPaint.setColor(arcColor);
mArcPaint.setAntiAlias(true);
mArcPaint.setStyle(Paint.Style.STROKE);
mArcPaint.setStrokeWidth(mArcWidth);
// mArcPaint.setAlpha(45);
mProgressPaint = new Paint();
mProgressPaint.setColor(progressColor);
mProgressPaint.setAntiAlias(true);
mProgressPaint.setStyle(Paint.Style.STROKE);
mProgressPaint.setStrokeWidth(mProgressWidth);
if (mRoundedEdges) {
mArcPaint.setStrokeCap(Paint.Cap.ROUND);
mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
}
}
private void onProgressRefresh(int progress, boolean fromUser) {
updateProgress(progress, fromUser);
}
private void onStartTrackingTouch() {
if (mOnSeekArcChangeListener != null) {
mOnSeekArcChangeListener.onStartTrackingTouch(this);
}
}
private void onStopTrackingTouch() {
if (mOnSeekArcChangeListener != null) {
mOnSeekArcChangeListener.onStopTrackingTouch(this);
}
}
private void updateOnTouch(MotionEvent event) {
boolean ignoreTouch = ignoreTouch(event.getX(), event.getY());
if (ignoreTouch) {
return;
}
setPressed(true);
mTouchAngle = getTouchDegrees(event.getX(), event.getY());
int progress = getProgressForAngle(mTouchAngle);
onProgressRefresh(progress, true);
}
private void updateProgress(int progress, boolean fromUser) {
if (progress == INVALID_PROGRESS_VALUE) {
return;
}
public int getArcColor() {
return mArcPaint.getColor();
progress = (progress > mMax) ? mMax : progress;
progress = (progress < 0) ? 0 : progress;
mProgress = progress;
if (mOnSeekArcChangeListener != null) {
mOnSeekArcChangeListener.onProgressChanged(this, progress, fromUser);
}
public void setArcColor(int color) {
mArcPaint.setColor(color);
invalidate();
}
mProgressSweep = (float) progress / mMax * mSweepAngle;
public int getArcRotation() {
return mRotation;
}
updateThumbPosition();
public void setArcRotation(int mRotation) {
this.mRotation = mRotation;
updateThumbPosition();
}
invalidate();
}
public int getArcWidth() {
return mArcWidth;
}
private void updateThumbPosition() {
int thumbAngle = (int) (mStartAngle + mProgressSweep + mRotation + 90);
mThumbXPos = (int) (mArcRadius * Math.cos(Math.toRadians(thumbAngle)));
mThumbYPos = (int) (mArcRadius * Math.sin(Math.toRadians(thumbAngle)));
}
public void setArcWidth(int mArcWidth) {
this.mArcWidth = mArcWidth;
mArcPaint.setStrokeWidth(mArcWidth);
}
private float valuePerDegree() {
return (float) mMax / mSweepAngle;
}
public int getMax() {
return mMax;
}
public void setMax(int mMax) {
this.mMax = mMax;
}
public int getProgress() {
return mProgress;
}
public void setProgress(int progress) {
updateProgress(progress, false);
}
public int getProgressColor() {
return mProgressPaint.getColor();
}
public void setProgressColor(int color) {
mProgressPaint.setColor(color);
invalidate();
}
public int getProgressWidth() {
return mProgressWidth;
}
public void setProgressWidth(int mProgressWidth) {
this.mProgressWidth = mProgressWidth;
mProgressPaint.setStrokeWidth(mProgressWidth);
}
public int getStartAngle() {
return mStartAngle;
}
public void setStartAngle(int mStartAngle) {
this.mStartAngle = mStartAngle;
updateThumbPosition();
}
public int getSweepAngle() {
return mSweepAngle;
}
public void setSweepAngle(int mSweepAngle) {
this.mSweepAngle = mSweepAngle;
updateThumbPosition();
}
public boolean isClockwise() {
return mClockwise;
}
public void setClockwise(boolean isClockwise) {
mClockwise = isClockwise;
}
public boolean isEnabled() {
return mEnabled;
}
public void setEnabled(boolean enabled) {
this.mEnabled = enabled;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mEnabled) {
this.getParent().requestDisallowInterceptTouchEvent(true);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
onStartTrackingTouch();
updateOnTouch(event);
break;
case MotionEvent.ACTION_MOVE:
updateOnTouch(event);
break;
case MotionEvent.ACTION_UP:
onStopTrackingTouch();
setPressed(false);
this.getParent().requestDisallowInterceptTouchEvent(false);
break;
case MotionEvent.ACTION_CANCEL:
onStopTrackingTouch();
setPressed(false);
this.getParent().requestDisallowInterceptTouchEvent(false);
break;
}
return true;
}
return false;
}
public interface OnSeekArcChangeListener {
/**
* Sets a listener to receive notifications of changes to the SeekArc's
* progress level. Also provides notifications of when the user starts and
* stops a touch gesture within the SeekArc.
* Notification that the progress level has changed. Clients can use the fromUser parameter to
* distinguish user-initiated changes from those that occurred programmatically.
*
* @param l The seek bar notification listener
* @see SeekArc.OnSeekBarChangeListener
* @param seekArc The SeekArc whose progress has changed
* @param progress The current progress level. This will be in the range 0..max where max was
* set by {@link ProgressArc#setMax(int)}. (The default value for max is 100.)
* @param fromUser True if the progress change was initiated by the user.
*/
public void setOnSeekArcChangeListener(OnSeekArcChangeListener l) {
mOnSeekArcChangeListener = l;
}
void onProgressChanged(SeekArc seekArc, int progress, boolean fromUser);
public void setRoundedEdges(boolean isEnabled) {
mRoundedEdges = isEnabled;
if (mRoundedEdges) {
mArcPaint.setStrokeCap(Paint.Cap.ROUND);
mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
} else {
mArcPaint.setStrokeCap(Paint.Cap.SQUARE);
mProgressPaint.setStrokeCap(Paint.Cap.SQUARE);
}
}
/**
* Notification that the user has started a touch gesture. Clients may want to use this to
* disable advancing the seekbar.
*
* @param seekArc The SeekArc in which the touch gesture began
*/
void onStartTrackingTouch(SeekArc seekArc);
public void setTouchInSide(boolean isEnabled) {
int thumbHalfheight = (int) mThumb.getIntrinsicHeight() / 2;
int thumbHalfWidth = (int) mThumb.getIntrinsicWidth() / 2;
mTouchInside = isEnabled;
if (mTouchInside) {
mTouchIgnoreRadius = (float) mArcRadius / 4;
} else {
// Don't use the exact radius makes interaction too tricky
mTouchIgnoreRadius = mArcRadius
- Math.min(thumbHalfWidth, thumbHalfheight);
}
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mThumb != null && mThumb.isStateful()) {
int[] state = getDrawableState();
mThumb.setState(state);
}
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
if (!mClockwise) {
canvas.scale(-1, 1, mArcRect.centerX(), mArcRect.centerY());
}
// Draw the arcs
final int arcStart = mStartAngle + mAngleOffset + mRotation;
final int arcSweep = mSweepAngle;
canvas.drawArc(mArcRect, arcStart, arcSweep, false, mArcPaint);
canvas.drawArc(mArcRect, arcStart, mProgressSweep, false,
mProgressPaint);
if (mEnabled) {
// Draw the thumb nail
canvas.translate(mTranslateX - mThumbXPos, mTranslateY - mThumbYPos);
mThumb.draw(canvas);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int height = getDefaultSize(getSuggestedMinimumHeight(),
heightMeasureSpec);
final int width = getDefaultSize(getSuggestedMinimumWidth(),
widthMeasureSpec);
final int min = Math.min(width, height);
float top = 0;
float left = 0;
int arcDiameter = 0;
mTranslateX = (int) (width * 0.5f);
mTranslateY = (int) (height * 0.5f);
arcDiameter = min - getPaddingLeft();
mArcRadius = arcDiameter / 2;
top = height / 2 - (arcDiameter / 2);
left = width / 2 - (arcDiameter / 2);
mArcRect.set(left, top, left + arcDiameter, top + arcDiameter);
int arcStart = (int) mProgressSweep + mStartAngle + mRotation + 90;
mThumbXPos = (int) (mArcRadius * Math.cos(Math.toRadians(arcStart)));
mThumbYPos = (int) (mArcRadius * Math.sin(Math.toRadians(arcStart)));
setTouchInSide(mTouchInside);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private int getProgressForAngle(double angle) {
int touchProgress = (int) Math.round(valuePerDegree() * angle);
touchProgress = (touchProgress < 0) ? INVALID_PROGRESS_VALUE
: touchProgress;
touchProgress = (touchProgress > mMax) ? INVALID_PROGRESS_VALUE
: touchProgress;
return touchProgress;
}
private double getTouchDegrees(float xPos, float yPos) {
float x = xPos - mTranslateX;
float y = yPos - mTranslateY;
//invert the x-coord if we are rotating anti-clockwise
x = (mClockwise) ? x : -x;
// convert to arc Angle
double angle = Math.toDegrees(Math.atan2(y, x) + (Math.PI / 2)
- Math.toRadians(mRotation));
if (angle < 0) {
angle = 360 + angle;
}
angle -= mStartAngle;
return angle;
}
private boolean ignoreTouch(float xPos, float yPos) {
boolean ignore = false;
float x = xPos - mTranslateX;
float y = yPos - mTranslateY;
float touchRadius = (float) Math.sqrt(((x * x) + (y * y)));
if (touchRadius < mTouchIgnoreRadius) {
ignore = true;
}
return ignore;
}
private void init(Context context, AttributeSet attrs, int defStyle) {
Log.d(TAG, "Initialising SeekArc");
final Resources res = getResources();
float density = context.getResources().getDisplayMetrics().density;
// Defaults, may need to link this into theme settings
int arcColor = res.getColor(R.color.progress_gray);
int progressColor = res.getColor(R.color.default_blue_light);
int thumbHalfheight = 0;
int thumbHalfWidth = 0;
mThumb = res.getDrawable(R.drawable.switch_thumb_material);
// Convert progress width to pixels for current density
mProgressWidth = (int) (mProgressWidth * density);
if (attrs != null) {
// Attribute initialization
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.SeekArc, defStyle, 0);
Drawable thumb = a.getDrawable(R.styleable.SeekArc_thumb);
if (thumb != null) {
mThumb = thumb;
}
thumbHalfheight = (int) mThumb.getIntrinsicHeight() / 2;
thumbHalfWidth = (int) mThumb.getIntrinsicWidth() / 2;
mThumb.setBounds(-thumbHalfWidth, -thumbHalfheight, thumbHalfWidth,
thumbHalfheight);
mMax = a.getInteger(R.styleable.SeekArc_max, mMax);
mProgress = a.getInteger(R.styleable.SeekArc_seekProgress, mProgress);
mProgressWidth = (int) a.getDimension(
R.styleable.SeekArc_progressWidth, mProgressWidth);
mArcWidth = (int) a.getDimension(R.styleable.SeekArc_arcWidth,
mArcWidth);
mStartAngle = a.getInt(R.styleable.SeekArc_startAngle, mStartAngle);
mSweepAngle = a.getInt(R.styleable.SeekArc_sweepAngle, mSweepAngle);
mRotation = a.getInt(R.styleable.SeekArc_rotation, mRotation);
mRoundedEdges = a.getBoolean(R.styleable.SeekArc_roundEdges,
mRoundedEdges);
mTouchInside = a.getBoolean(R.styleable.SeekArc_touchInside,
mTouchInside);
mClockwise = a.getBoolean(R.styleable.SeekArc_clockwise,
mClockwise);
mEnabled = a.getBoolean(R.styleable.SeekArc_enabled, mEnabled);
arcColor = a.getColor(R.styleable.SeekArc_arcColor, arcColor);
progressColor = a.getColor(R.styleable.SeekArc_progressColor,
progressColor);
a.recycle();
}
mProgress = (mProgress > mMax) ? mMax : mProgress;
mProgress = (mProgress < 0) ? 0 : mProgress;
mSweepAngle = (mSweepAngle > 360) ? 360 : mSweepAngle;
mSweepAngle = (mSweepAngle < 0) ? 0 : mSweepAngle;
mProgressSweep = (float) mProgress / mMax * mSweepAngle;
mStartAngle = (mStartAngle > 360) ? 0 : mStartAngle;
mStartAngle = (mStartAngle < 0) ? 0 : mStartAngle;
mArcPaint = new Paint();
mArcPaint.setColor(arcColor);
mArcPaint.setAntiAlias(true);
mArcPaint.setStyle(Paint.Style.STROKE);
mArcPaint.setStrokeWidth(mArcWidth);
//mArcPaint.setAlpha(45);
mProgressPaint = new Paint();
mProgressPaint.setColor(progressColor);
mProgressPaint.setAntiAlias(true);
mProgressPaint.setStyle(Paint.Style.STROKE);
mProgressPaint.setStrokeWidth(mProgressWidth);
if (mRoundedEdges) {
mArcPaint.setStrokeCap(Paint.Cap.ROUND);
mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
}
}
private void onProgressRefresh(int progress, boolean fromUser) {
updateProgress(progress, fromUser);
}
private void onStartTrackingTouch() {
if (mOnSeekArcChangeListener != null) {
mOnSeekArcChangeListener.onStartTrackingTouch(this);
}
}
private void onStopTrackingTouch() {
if (mOnSeekArcChangeListener != null) {
mOnSeekArcChangeListener.onStopTrackingTouch(this);
}
}
private void updateOnTouch(MotionEvent event) {
boolean ignoreTouch = ignoreTouch(event.getX(), event.getY());
if (ignoreTouch) {
return;
}
setPressed(true);
mTouchAngle = getTouchDegrees(event.getX(), event.getY());
int progress = getProgressForAngle(mTouchAngle);
onProgressRefresh(progress, true);
}
private void updateProgress(int progress, boolean fromUser) {
if (progress == INVALID_PROGRESS_VALUE) {
return;
}
progress = (progress > mMax) ? mMax : progress;
progress = (progress < 0) ? 0 : progress;
mProgress = progress;
if (mOnSeekArcChangeListener != null) {
mOnSeekArcChangeListener
.onProgressChanged(this, progress, fromUser);
}
mProgressSweep = (float) progress / mMax * mSweepAngle;
updateThumbPosition();
invalidate();
}
private void updateThumbPosition() {
int thumbAngle = (int) (mStartAngle + mProgressSweep + mRotation + 90);
mThumbXPos = (int) (mArcRadius * Math.cos(Math.toRadians(thumbAngle)));
mThumbYPos = (int) (mArcRadius * Math.sin(Math.toRadians(thumbAngle)));
}
private float valuePerDegree() {
return (float) mMax / mSweepAngle;
}
public interface OnSeekArcChangeListener {
/**
* Notification that the progress level has changed. Clients can use the
* fromUser parameter to distinguish user-initiated changes from those
* that occurred programmatically.
*
* @param seekArc The SeekArc whose progress has changed
* @param progress The current progress level. This will be in the range
* 0..max where max was set by
* {@link ProgressArc#setMax(int)}. (The default value for
* max is 100.)
* @param fromUser True if the progress change was initiated by the user.
*/
void onProgressChanged(SeekArc seekArc, int progress, boolean fromUser);
/**
* Notification that the user has started a touch gesture. Clients may
* want to use this to disable advancing the seekbar.
*
* @param seekArc The SeekArc in which the touch gesture began
*/
void onStartTrackingTouch(SeekArc seekArc);
/**
* Notification that the user has finished a touch gesture. Clients may
* want to use this to re-enable advancing the seekarc.
*
* @param seekArc The SeekArc in which the touch gesture began
*/
void onStopTrackingTouch(SeekArc seekArc);
}
}
/**
* Notification that the user has finished a touch gesture. Clients may want to use this to
* re-enable advancing the seekarc.
*
* @param seekArc The SeekArc in which the touch gesture began
*/
void onStopTrackingTouch(SeekArc seekArc);
}
}

View file

@ -19,33 +19,32 @@ import android.os.Build;
import android.util.AttributeSet;
import android.view.WindowInsets;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
public class StatusBarMarginFrameLayout extends FrameLayout {
public StatusBarMarginFrameLayout(@NonNull Context context) {
super(context);
}
public StatusBarMarginFrameLayout(@NonNull Context context) {
super(context);
}
public StatusBarMarginFrameLayout(@NonNull Context context, @NonNull AttributeSet attrs) {
super(context, attrs);
}
public StatusBarMarginFrameLayout(@NonNull Context context, @NonNull AttributeSet attrs) {
super(context, attrs);
}
public StatusBarMarginFrameLayout(
@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public StatusBarMarginFrameLayout(@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@NonNull
@Override
public WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
lp.topMargin = insets.getSystemWindowInsetTop();
lp.bottomMargin = insets.getSystemWindowInsetBottom();
setLayoutParams(lp);
}
return super.onApplyWindowInsets(insets);
@NonNull
@Override
public WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
lp.topMargin = insets.getSystemWindowInsetTop();
lp.bottomMargin = insets.getSystemWindowInsetBottom();
setLayoutParams(lp);
}
return super.onApplyWindowInsets(insets);
}
}

View file

@ -18,42 +18,38 @@ import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull;
public class StatusBarView extends View {
public StatusBarView(@NonNull Context context) {
super(context);
init(context);
}
public StatusBarView(@NonNull Context context) {
super(context);
init(context);
public StatusBarView(@NonNull Context context, @NonNull AttributeSet attrs) {
super(context, attrs);
init(context);
}
public StatusBarView(@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
public static int getStatusBarHeight(@NonNull Resources r) {
int result = 0;
int resourceId = r.getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = r.getDimensionPixelSize(resourceId);
}
return result;
}
public StatusBarView(@NonNull Context context, @NonNull AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {}
public StatusBarView(@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
public static int getStatusBarHeight(@NonNull Resources r) {
int result = 0;
int resourceId = r.getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = r.getDimensionPixelSize(resourceId);
}
return result;
}
private void init(Context context) {
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), getStatusBarHeight(getResources()));
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), getStatusBarHeight(getResources()));
}
}

View file

@ -19,50 +19,46 @@ import android.graphics.Canvas;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.Gravity;
import androidx.appcompat.widget.AppCompatTextView;
public class VerticalTextView extends AppCompatTextView {
final boolean topDown;
final boolean topDown;
public VerticalTextView(Context context, AttributeSet attrs) {
super(context, attrs);
final int gravity = getGravity();
if (Gravity.isVertical(gravity) && (gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
setGravity((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) | Gravity.TOP);
topDown = false;
} else
topDown = true;
public VerticalTextView(Context context, AttributeSet attrs) {
super(context, attrs);
final int gravity = getGravity();
if (Gravity.isVertical(gravity)
&& (gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
setGravity((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) | Gravity.TOP);
topDown = false;
} else topDown = true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
@Override
protected void onDraw(Canvas canvas) {
TextPaint textPaint = getPaint();
textPaint.setColor(getCurrentTextColor());
textPaint.drawableState = getDrawableState();
canvas.save();
if (topDown) {
canvas.translate(getWidth(), 0);
canvas.rotate(90);
} else {
canvas.translate(0, getHeight());
canvas.rotate(-90);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
canvas.translate(getCompoundPaddingLeft(), getExtendedPaddingTop());
@Override
protected void onDraw(Canvas canvas) {
TextPaint textPaint = getPaint();
textPaint.setColor(getCurrentTextColor());
textPaint.drawableState = getDrawableState();
canvas.save();
if (topDown) {
canvas.translate(getWidth(), 0);
canvas.rotate(90);
} else {
canvas.translate(0, getHeight());
canvas.rotate(-90);
}
canvas.translate(getCompoundPaddingLeft(), getExtendedPaddingTop());
getLayout().draw(canvas);
canvas.restore();
}
}
getLayout().draw(canvas);
canvas.restore();
}
}