This commit is contained in:
2025-11-18 15:12:24 +08:00
parent c173c425ce
commit a7d2035e70
1361 changed files with 367452 additions and 0 deletions
@@ -0,0 +1,431 @@
package cn.shangyu.gdxzExpert.polyvplayer.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
/**
* A simple extension of a regular linear layout that supports the divider API
* of Android 4.0+. The dividers are added adjacent to the children by changing
* their layout params. If you need to rely on the margins which fall in the
* same orientation as the layout you should wrap the child in a simple
* {@link android.widget.FrameLayout} so it can receive the margin.
*/
public class IcsLinearLayout extends LinearLayout {
private static final int[] R_styleable_LinearLayout = new int[] {
/* 0 */ android.R.attr.divider,
/* 1 */ android.R.attr.measureWithLargestChild,
/* 2 */ android.R.attr.showDividers,
/* 3 */ android.R.attr.dividerPadding,
};
private static final int LinearLayout_divider = 0;
private static final int LinearLayout_measureWithLargestChild = 1;
private static final int LinearLayout_showDividers = 2;
private static final int LinearLayout_dividerPadding = 3;
/**
* Don't show any dividers.
*/
public static final int SHOW_DIVIDER_NONE = 0;
/**
* Show a divider at the beginning of the group.
*/
public static final int SHOW_DIVIDER_BEGINNING = 1;
/**
* Show dividers between each item in the group.
*/
public static final int SHOW_DIVIDER_MIDDLE = 2;
/**
* Show a divider at the end of the group.
*/
public static final int SHOW_DIVIDER_END = 4;
private static final boolean IS_HONEYCOMB = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
private Drawable mDivider;
protected int mDividerWidth;
protected int mDividerHeight;
private int mShowDividers;
private int mDividerPadding;
private boolean mClipDivider;
private boolean mUseLargestChild;
public IcsLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, /*com.android.internal.R.styleable.*/R_styleable_LinearLayout);
setDividerDrawable(a.getDrawable(/*com.android.internal.R.styleable.*/LinearLayout_divider));
mShowDividers = a.getInt(/*com.android.internal.R.styleable.*/LinearLayout_showDividers, SHOW_DIVIDER_NONE);
mDividerPadding = a.getDimensionPixelSize(/*com.android.internal.R.styleable.*/LinearLayout_dividerPadding, 0);
mUseLargestChild = a.getBoolean(/*com.android.internal.R.styleable.*/LinearLayout_measureWithLargestChild, false);
a.recycle();
}
/**
* Set how dividers should be shown between items in this layout
*
* @param showDividers One or more of {@link #SHOW_DIVIDER_BEGINNING},
* {@link #SHOW_DIVIDER_MIDDLE}, or {@link #SHOW_DIVIDER_END},
* or {@link #SHOW_DIVIDER_NONE} to show no dividers.
*/
public void setShowDividers(int showDividers) {
if (showDividers != mShowDividers) {
requestLayout();
invalidate(); //XXX This is required if you are toggling a divider off
}
mShowDividers = showDividers;
}
/**
* @return A flag set indicating how dividers should be shown around items.
* @see #setShowDividers(int)
*/
public int getShowDividers() {
return mShowDividers;
}
/**
* Set a drawable to be used as a divider between items.
* @param divider Drawable that will divide each item.
* @see #setShowDividers(int)
*/
public void setDividerDrawable(Drawable divider) {
if (divider == mDivider) {
return;
}
mDivider = divider;
mClipDivider = divider instanceof ColorDrawable;
if (divider != null) {
mDividerWidth = divider.getIntrinsicWidth();
mDividerHeight = divider.getIntrinsicHeight();
} else {
mDividerWidth = 0;
mDividerHeight = 0;
}
setWillNotDraw(divider == null);
requestLayout();
}
/**
* Set padding displayed on both ends of dividers.
*
* @param padding Padding value in pixels that will be applied to each end
*
* @see #setShowDividers(int)
* @see #setDividerDrawable(Drawable)
* @see #getDividerPadding()
*/
public void setDividerPadding(int padding) {
mDividerPadding = padding;
}
/**
* Get the padding size used to inset dividers in pixels
*
* @see #setShowDividers(int)
* @see #setDividerDrawable(Drawable)
* @see #setDividerPadding(int)
*/
public int getDividerPadding() {
return mDividerPadding;
}
/**
* Get the width of the current divider drawable.
*
* @hide Used internally by framework.
*/
public int getDividerWidth() {
return mDividerWidth;
}
@Override
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
final int index = indexOfChild(child);
final int orientation = getOrientation();
final LayoutParams params = (LayoutParams) child.getLayoutParams();
if (hasDividerBeforeChildAt(index)) {
if (orientation == VERTICAL) {
//Account for the divider by pushing everything up
params.topMargin = mDividerHeight;
} else {
//Account for the divider by pushing everything left
params.leftMargin = mDividerWidth;
}
}
final int count = getChildCount();
if (index == count - 1) {
if (hasDividerBeforeChildAt(count)) {
if (orientation == VERTICAL) {
params.bottomMargin = mDividerHeight;
} else {
params.rightMargin = mDividerWidth;
}
}
}
super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
}
@Override
protected void onDraw(Canvas canvas) {
if (mDivider != null) {
if (getOrientation() == VERTICAL) {
drawDividersVertical(canvas);
} else {
drawDividersHorizontal(canvas);
}
}
super.onDraw(canvas);
}
void drawDividersVertical(Canvas canvas) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child != null && child.getVisibility() != GONE) {
if (hasDividerBeforeChildAt(i)) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int top = child.getTop() - lp.topMargin/* - mDividerHeight*/;
drawHorizontalDivider(canvas, top);
}
}
}
if (hasDividerBeforeChildAt(count)) {
final View child = getChildAt(count - 1);
int bottom = 0;
if (child == null) {
bottom = getHeight() - getPaddingBottom() - mDividerHeight;
} else {
//final LayoutParams lp = (LayoutParams) child.getLayoutParams();
bottom = child.getBottom()/* + lp.bottomMargin*/;
}
drawHorizontalDivider(canvas, bottom);
}
}
void drawDividersHorizontal(Canvas canvas) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child != null && child.getVisibility() != GONE) {
if (hasDividerBeforeChildAt(i)) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int left = child.getLeft() - lp.leftMargin/* - mDividerWidth*/;
drawVerticalDivider(canvas, left);
}
}
}
if (hasDividerBeforeChildAt(count)) {
final View child = getChildAt(count - 1);
int right = 0;
if (child == null) {
right = getWidth() - getPaddingRight() - mDividerWidth;
} else {
//final LayoutParams lp = (LayoutParams) child.getLayoutParams();
right = child.getRight()/* + lp.rightMargin*/;
}
drawVerticalDivider(canvas, right);
}
}
void drawHorizontalDivider(Canvas canvas, int top) {
if(mClipDivider && !IS_HONEYCOMB) {
canvas.save();
canvas.clipRect(getPaddingLeft() + mDividerPadding, top,
getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight);
mDivider.draw(canvas);
canvas.restore();
} else {
mDivider.setBounds(getPaddingLeft() + mDividerPadding, top,
getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight);
mDivider.draw(canvas);
}
}
void drawVerticalDivider(Canvas canvas, int left) {
if(mClipDivider && !IS_HONEYCOMB) {
canvas.save();
canvas.clipRect(left, getPaddingTop() + mDividerPadding,
left + mDividerWidth, getHeight() - getPaddingBottom() - mDividerPadding);
mDivider.draw(canvas);
canvas.restore();
} else {
mDivider.setBounds(left, getPaddingTop() + mDividerPadding,
left + mDividerWidth, getHeight() - getPaddingBottom() - mDividerPadding);
mDivider.draw(canvas);
}
}
/**
* Determines where to position dividers between children.
*
* @param childIndex Index of child to check for preceding divider
* @return true if there should be a divider before the child at childIndex
* @hide Pending API consideration. Currently only used internally by the system.
*/
protected boolean hasDividerBeforeChildAt(int childIndex) {
if (childIndex == 0) {
return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0;
} else if (childIndex == getChildCount()) {
return (mShowDividers & SHOW_DIVIDER_END) != 0;
} else if ((mShowDividers & SHOW_DIVIDER_MIDDLE) != 0) {
boolean hasVisibleViewBefore = false;
for (int i = childIndex - 1; i >= 0; i--) {
if (getChildAt(i).getVisibility() != GONE) {
hasVisibleViewBefore = true;
break;
}
}
return hasVisibleViewBefore;
}
return false;
}
/**
* When true, all children with a weight will be considered having
* the minimum size of the largest child. If false, all children are
* measured normally.
*
* @return True to measure children with a weight using the minimum
* size of the largest child, false otherwise.
*
* @attr ref android.R.styleable#LinearLayout_measureWithLargestChild
*/
public boolean isMeasureWithLargestChildEnabled() {
return mUseLargestChild;
}
/**
* When set to true, all children with a weight will be considered having
* the minimum size of the largest child. If false, all children are
* measured normally.
*
* Disabled by default.
*
* @param enabled True to measure children with a weight using the
* minimum size of the largest child, false otherwise.
*
* @attr ref android.R.styleable#LinearLayout_measureWithLargestChild
*/
public void setMeasureWithLargestChildEnabled(boolean enabled) {
mUseLargestChild = enabled;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mUseLargestChild) {
final int orientation = getOrientation();
switch (orientation) {
case HORIZONTAL:
useLargestChildHorizontal();
break;
case VERTICAL:
useLargestChildVertical();
break;
}
}
}
private void useLargestChildHorizontal() {
final int childCount = getChildCount();
// Find largest child width
int largestChildWidth = 0;
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
largestChildWidth = Math.max(child.getMeasuredWidth(), largestChildWidth);
}
int totalWidth = 0;
// Re-measure childs
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
final LayoutParams lp =
(LayoutParams) child.getLayoutParams();
float childExtra = lp.weight;
if (childExtra > 0) {
child.measure(
MeasureSpec.makeMeasureSpec(largestChildWidth,
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(),
MeasureSpec.EXACTLY));
totalWidth += largestChildWidth;
} else {
totalWidth += child.getMeasuredWidth();
}
totalWidth += lp.leftMargin + lp.rightMargin;
}
totalWidth += getPaddingLeft() + getPaddingRight();
setMeasuredDimension(totalWidth, getMeasuredHeight());
}
private void useLargestChildVertical() {
final int childCount = getChildCount();
// Find largest child width
int largestChildHeight = 0;
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
largestChildHeight = Math.max(child.getMeasuredHeight(), largestChildHeight);
}
int totalHeight = 0;
// Re-measure childs
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
final LayoutParams lp =
(LayoutParams) child.getLayoutParams();
float childExtra = lp.weight;
if (childExtra > 0) {
child.measure(
MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(largestChildHeight,
MeasureSpec.EXACTLY));
totalHeight += largestChildHeight;
} else {
totalHeight += child.getMeasuredHeight();
}
totalHeight += lp.leftMargin + lp.rightMargin;
}
totalHeight += getPaddingLeft() + getPaddingRight();
setMeasuredDimension(getMeasuredWidth(), totalHeight);
}
}
@@ -0,0 +1,356 @@
package cn.shangyu.gdxzExpert.polyvplayer.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.view.SoundEffectConstants;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import cn.shangyu.gdxzExpert.R;
/**
* An extension of a linear layout that supports the divider API of Android
* 4.0+. You can populate this layout with data that comes from a
* {@link ListAdapter}
*/
public class LinearListView extends IcsLinearLayout {
private static final int[] R_styleable_LinearListView = new int[] {
/* 0 */android.R.attr.entries,
/* 1 */R.attr.dividerThickness
};
private static final int LinearListView_entries = 0;
private static final int LinearListView_dividerThickness = 1;
private View mEmptyView;
private ListAdapter mAdapter;
private boolean mAreAllItemsSelectable;
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;
private DataSetObserver mDataObserver = new DataSetObserver() {
@Override
public void onChanged() {
setupChildren();
}
@Override
public void onInvalidated() {
setupChildren();
}
};
public LinearListView(Context context) {
this(context, null);
}
public LinearListView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R_styleable_LinearListView);
// Use the thickness specified, zero being the default
final int thickness = a.getDimensionPixelSize(
LinearListView_dividerThickness, 0);
if (thickness != 0) {
setDividerThickness(thickness);
}
CharSequence[] entries = a.getTextArray(LinearListView_entries);
if (entries != null) {
setAdapter(new ArrayAdapter<CharSequence>(context,
android.R.layout.simple_list_item_1, entries));
}
a.recycle();
}
@Override
public void setOrientation(int orientation) {
if (orientation != getOrientation()) {
int tmp = mDividerHeight;
mDividerHeight = mDividerWidth;
mDividerWidth = tmp;
}
super.setOrientation(orientation);
}
/**
* Set the divider thickness size in pixel. That means setting the divider
* height if the layout has an HORIZONTAL orientation and setting the
* divider width otherwise.
*
* @param thickness
* The divider thickness in pixel.
*/
public void setDividerThickness(int thickness) {
if (getOrientation() == VERTICAL) {
mDividerHeight = thickness;
} else {
mDividerWidth = thickness;
}
requestLayout();
}
public ListAdapter getAdapter() {
return mAdapter;
}
/**
* Sets the data behind this LinearListView.
*
* @param adapter
* The ListAdapter which is responsible for maintaining the data
* backing this list and for producing a view to represent an
* item in that data set.
*
* @see #getAdapter()
*/
public void setAdapter(ListAdapter adapter) {
if (mAdapter != null) {
mAdapter.unregisterDataSetObserver(mDataObserver);
}
mAdapter = adapter;
if (mAdapter != null) {
mAdapter.registerDataSetObserver(mDataObserver);
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
}
setupChildren();
}
/**
* Interface definition for a callback to be invoked when an item in this
* LinearListView has been clicked.
*/
public interface OnItemClickListener {
/**
* Callback method to be invoked when an item in this LinearListView has
* been clicked.
* <p>
* Implementers can call getItemAtPosition(position) if they need to
* access the data associated with the selected item.
*
* @param parent
* The LinearListView where the click happened.
* @param view
* The view within the LinearListView that was clicked (this
* will be a view provided by the adapter)
* @param position
* The position of the view in the adapter.
* @param id
* The row id of the item that was clicked.
*/
void onItemClick(LinearListView parent, View view, int position, long id);
}
/**
* Register a callback to be invoked when an item in this LinearListView has
* been clicked.
*
* @param listener
* The callback that will be invoked.
*/
public void setOnItemClickListener(OnItemClickListener listener) {
mOnItemClickListener = listener;
}
/**
* @return The callback to be invoked with an item in this LinearListView has
* been clicked, or null id no callback has been set.
*/
public final OnItemClickListener getOnItemClickListener() {
return mOnItemClickListener;
}
/**
* Call the OnItemClickListener, if it is defined.
*
* @param view
* The view within the LinearListView that was clicked.
* @param position
* The position of the view in the adapter.
* @param id
* The row id of the item that was clicked.
* @return True if there was an assigned OnItemClickListener that was
* called, false otherwise is returned.
*/
public boolean performItemClick(View view, int position, long id) {
if (mOnItemClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnItemClickListener.onItemClick(this, view, position, id);
return true;
}
return false;
}
/**
* Sets the view to show if the adapter is empty
*/
public void setEmptyView(View emptyView) {
mEmptyView = emptyView;
final ListAdapter adapter = getAdapter();
final boolean empty = ((adapter == null) || adapter.isEmpty());
updateEmptyStatus(empty);
}
/**
* When the current adapter is empty, the LinearListView can display a special
* view call the empty view. The empty view is used to provide feedback to
* the user that no data is available in this LinearListView.
*
* @return The view to show if the adapter is empty.
*/
public View getEmptyView() {
return mEmptyView;
}
/**
* Update the status of the list based on the empty parameter. If empty is
* true and we have an empty view, display it. In all the other cases, make
* sure that the layout is VISIBLE and that the empty view is GONE (if
* it's not null).
*/
private void updateEmptyStatus(boolean empty) {
if (empty) {
if (mEmptyView != null) {
mEmptyView.setVisibility(View.VISIBLE);
setVisibility(View.GONE);
} else {
// If the caller just removed our empty view, make sure the list
// view is visible
setVisibility(View.VISIBLE);
}
} else {
if (mEmptyView != null)
mEmptyView.setVisibility(View.GONE);
setVisibility(View.VISIBLE);
}
}
private void setupChildren() {
removeAllViews();
updateEmptyStatus((mAdapter == null) || mAdapter.isEmpty());
if (mAdapter == null) {
return;
}
for (int i = 0; i < mAdapter.getCount(); i++) {
// adapter 要使用inflate(layoutId, parent, false);
View child = mAdapter.getView(i, null, this);
if (mAreAllItemsSelectable || mAdapter.isEnabled(i)) {
child.setOnClickListener(new InternalOnClickListener(i));
child.setOnLongClickListener(new InternalOnLongClickListener(i));
}
addViewInLayout(child, -1, child.getLayoutParams(), true);
}
}
/**
* Internal OnClickListener that this view associate of each of its children
* so that they can respond to OnItemClick listener's events. Avoid setting
* an OnClickListener manually. If you need it you can wrap the child in a
* simple {@link android.widget.FrameLayout}.
*/
private class InternalOnClickListener implements OnClickListener {
int mPosition;
public InternalOnClickListener(int position) {
mPosition = position;
}
@Override
public void onClick(View v) {
if ((mOnItemClickListener != null) && (mAdapter != null)) {
mOnItemClickListener.onItemClick(LinearListView.this, v,
mPosition, mAdapter.getItemId(mPosition));
}
}
}
/**
* Internal OnClickListener that this view associate of each of its children
* so that they can respond to OnItemClick listener's events. Avoid setting
* an OnClickListener manually. If you need it you can wrap the child in a
* simple {@link android.widget.FrameLayout}.
*/
private class InternalOnLongClickListener implements OnLongClickListener {
int mPosition;
public InternalOnLongClickListener(int position) {
mPosition = position;
}
@Override
public boolean onLongClick(View v) {
if ((mOnItemLongClickListener != null) && (mAdapter != null)) {
return mOnItemLongClickListener.onItemLongClick(LinearListView.this, v,
mPosition, mAdapter.getItemId(mPosition));
}
return false;
}
}
/**
* Interface definition for a callback to be invoked when an item in this
* LinearListView has been clicked.
*/
public interface OnItemLongClickListener {
/**
* Callback method to be invoked when an item in this LinearListView has
* been clicked.
* <p>
* Implementers can call getItemAtPosition(position) if they need to
* access the data associated with the selected item.
*
* @param parent
* The LinearListView where the click happened.
* @param view
* The view within the LinearListView that was clicked (this
* will be a view provided by the adapter)
* @param position
* The position of the view in the adapter.
* @param id
* The row id of the item that was clicked.
*/
boolean onItemLongClick(LinearListView parent, View view, int position, long id);
}
/**
* Register a callback to be invoked when an item in this LinearListView has
* been clicked.
*
* @param listener
* The callback that will be invoked.
*/
public void setOnItemLongClickListener(OnItemLongClickListener listener) {
mOnItemLongClickListener = listener;
}
/**
* @return The callback to be invoked with an item in this LinearListView has
* been clicked, or null id no callback has been set.
*/
public final OnItemLongClickListener getOnItemLongClickListener() {
return mOnItemLongClickListener;
}
}
@@ -0,0 +1,156 @@
package cn.shangyu.gdxzExpert.polyvplayer.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import cn.shangyu.gdxzExpert.R;
/**
* Created by zhuyong on 2017/6/22.
*/
public class PolyvCircleProgressView extends View {
private int mCurrent;//当前进度
private Paint mPaintOut;
private Paint mPaintCurrent;
private Paint mPaintText;
private float mPaintWidth;//画笔宽度
private int mPaintColor = Color.RED;//画笔颜色
private int mTextColor = Color.BLACK;//字体颜色
private float mTextSize;//字体大小
private int location;//从哪个位置开始
private float startAngle;//开始角度
private OnLoadingCompleteListener mLoadingCompleteListener;
public PolyvCircleProgressView(Context context) {
this(context, null);
}
public PolyvCircleProgressView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public PolyvCircleProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取属性值
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView);
location = array.getInt(R.styleable.CircleProgressView_location, 1);
mPaintWidth = array.getDimension(R.styleable.CircleProgressView_progress_paint_width, dip2px(context, 4));//默认4dp
mPaintColor = array.getColor(R.styleable.CircleProgressView_progress_paint_color, mPaintColor);
mTextSize = array.getDimension(R.styleable.CircleProgressView_progress_text_size, dip2px(context, 18));//默认18sp
mTextColor = array.getColor(R.styleable.CircleProgressView_progress_text_color, mTextColor);
array.recycle();
//画笔->背景圆弧
mPaintOut = new Paint();
mPaintOut.setAntiAlias(true);
mPaintOut.setStrokeWidth(mPaintWidth);
mPaintOut.setStyle(Paint.Style.STROKE);
mPaintOut.setColor(Color.parseColor("#1F1F1F"));
mPaintOut.setStrokeCap(Paint.Cap.ROUND);
//画笔->进度圆弧
mPaintCurrent = new Paint();
mPaintCurrent.setAntiAlias(true);
mPaintCurrent.setStrokeWidth(mPaintWidth);
mPaintCurrent.setStyle(Paint.Style.STROKE);
mPaintCurrent.setColor(mPaintColor);
// mPaintCurrent.setStrokeCap(Paint.Cap.ROUND);
//画笔->绘制字体
mPaintText = new Paint();
mPaintText.setAntiAlias(true);
mPaintText.setStyle(Paint.Style.FILL);
mPaintText.setColor(mTextColor);
mPaintText.setTextSize(mTextSize);
if (location == 1) {//默认从左侧开始
startAngle = -180;
} else if (location == 2) {
startAngle = -90;
} else if (location == 3) {
startAngle = 0;
} else if (location == 4) {
startAngle = 90;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int size = width > height ? height : width;
setMeasuredDimension(size, size);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制背景圆弧,因为画笔有一定的宽度,所有画圆弧的范围要比View本身的大小稍微小一些,不然画笔画出来的东西会显示不完整
RectF rectF = new RectF(mPaintWidth / 2, mPaintWidth / 2, getWidth() - mPaintWidth / 2, getHeight() - mPaintWidth / 2);
canvas.drawArc(rectF, 0, 360, false, mPaintOut);
//绘制当前进度
float sweepAngle = 360 * mCurrent / 100;
canvas.drawArc(rectF, startAngle, sweepAngle, false, mPaintCurrent);
// //绘制进度数字
// String text = mCurrent + "%";
// //获取文字宽度
// float textWidth = mPaintText.measureText(text, 0, text.length());
// float dx = getWidth() / 2 - textWidth / 2;
// Paint.FontMetricsInt fontMetricsInt = mPaintText.getFontMetricsInt();
// float dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
// float baseLine = getHeight() / 2 + dy;
// canvas.drawText(text, dx, baseLine, mPaintText);
if (mLoadingCompleteListener != null && mCurrent == 100) {
mLoadingCompleteListener.complete();
}
}
/**
* 获取当前进度值
*
* @return
*/
public int getmCurrent() {
return mCurrent;
}
/**
* 设置当前进度并重新绘制界面
*
* @param mCurrent
*/
public void setmCurrent(int mCurrent) {
this.mCurrent = mCurrent;
invalidate();
}
public void setOnLoadingCompleteListener(OnLoadingCompleteListener loadingCompleteListener) {
this.mLoadingCompleteListener = loadingCompleteListener;
}
public interface OnLoadingCompleteListener {
void complete();
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
@@ -0,0 +1,40 @@
package cn.shangyu.gdxzExpert.polyvplayer.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.ImageView;
import cn.shangyu.gdxzExpert.R;
@SuppressLint("AppCompatCustomView")
public class PolyvGrayImageView extends ImageView {
public PolyvGrayImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public PolyvGrayImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PolyvGrayImageView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isPressed())
// canvas.drawColor(0x33000000);
canvas.drawColor(getResources().getColor(R.color.commom_click_color_gray_half));
}
@Override
protected void dispatchSetPressed(boolean pressed) {
super.dispatchSetPressed(pressed);
invalidate();
}
}
@@ -0,0 +1,126 @@
package cn.shangyu.gdxzExpert.polyvplayer.view;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.easefun.polyvsdk.video.PolyvVideoView;
import java.util.Locale;
import cn.shangyu.gdxzExpert.R;
import cn.shangyu.gdxzExpert.polyvplayer.util.PolyvScreenUtils;
public class PolyvLoadingLayout extends FrameLayout {
private ProgressBar loadingProgress;
private TextView loadingSpeed;
private PolyvVideoView videoView;
private Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
if (videoView != null && !videoView.isLocalPlay()) {
loadingSpeed.setVisibility(View.VISIBLE);
loadingSpeed.setText(formatedSpeed(videoView.getTcpSpeed(), 1000));
handler.sendEmptyMessageDelayed(1, 500);
}
}
}
};
private static String formatedSpeed(long bytes, long elapsed_milli) {
if (elapsed_milli <= 0) {
return "0 B/S";
}
if (bytes <= 0) {
return "0 B/S";
}
float bytes_per_sec = ((float) bytes) * 1000.f / elapsed_milli;
if (bytes_per_sec >= 1000 * 1000) {
return String.format(Locale.US, "%.2f MB/S", ((float) bytes_per_sec) / 1000 / 1000);
} else if (bytes_per_sec >= 1000) {
return String.format(Locale.US, "%.2f KB/S", ((float) bytes_per_sec) / 1000);
} else {
return String.format(Locale.US, "%d B/S", (long) bytes_per_sec);
}
}
public PolyvLoadingLayout(@NonNull Context context) {
this(context, null);
}
public PolyvLoadingLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public PolyvLoadingLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater.from(context).inflate(R.layout.polyv_player_loading_layout, this);
initView();
}
private void initView() {
loadingProgress = (ProgressBar) findViewById(R.id.loading_progress);
loadingSpeed = (TextView) findViewById(R.id.loading_speed);
}
public void bindVideoView(PolyvVideoView videoView) {
this.videoView = videoView;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
handler.removeCallbacksAndMessages(null);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
acceptVisibilityChange(getVisibility());
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
acceptVisibilityChange(visibility);
}
private void acceptVisibilityChange(int visibility) {
handler.removeCallbacksAndMessages(null);
if (visibility == View.VISIBLE) {
handler.sendEmptyMessage(1);
} else {
loadingSpeed.setVisibility(View.GONE);
}
}
public void fitLocationChange(boolean isInMainScreen) {
if (loadingProgress == null)
return;
MarginLayoutParams rlp = (MarginLayoutParams) loadingProgress.getLayoutParams();
if (isInMainScreen) {
rlp.width = MarginLayoutParams.WRAP_CONTENT;
rlp.height = MarginLayoutParams.WRAP_CONTENT;
} else {
rlp.width = PolyvScreenUtils.dip2px(getContext(), 32);
rlp.height = PolyvScreenUtils.dip2px(getContext(), 32);
}
loadingProgress.setLayoutParams(rlp);
}
}
@@ -0,0 +1,41 @@
package cn.shangyu.gdxzExpert.polyvplayer.view;
import android.content.Context;
import android.os.Build;
import androidx.core.view.ViewCompat;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AbsListView;
public class PolyvSimpleSwipeRefreshLayout extends SwipeRefreshLayout {
// 包含的View
private View view;
public PolyvSimpleSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PolyvSimpleSwipeRefreshLayout(Context context) {
super(context);
}
public void setChildView(View view) {
this.view = view;
}
@Override
public boolean canChildScrollUp() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
if (view instanceof AbsListView) {
final AbsListView absListView = (AbsListView) view;
return absListView.getChildCount() > 0 && (absListView.getFirstVisiblePosition() > 0
|| absListView.getChildAt(0).getTop() < absListView.getPaddingTop());
} else {
return view.getScrollY() > 0;
}
} else {
return ViewCompat.canScrollVertically(view, -1);
}
}
}
@@ -0,0 +1,283 @@
package cn.shangyu.gdxzExpert.polyvplayer.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import androidx.appcompat.widget.AppCompatSeekBar;
import android.util.AttributeSet;
import android.view.MotionEvent;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import cn.shangyu.gdxzExpert.polyvplayer.util.PolyvScreenUtils;
public class PolyvTickSeekBar extends AppCompatSeekBar {
private int height, width;
//画笔
private Paint mStockPaint;
private List<TickData> mTickDataList;
private boolean isMoved;
private int moveCount;
private float fingerRadius;//触摸点的px范围的点视为点中打点
public PolyvTickSeekBar(Context context) {
this(context, null);
}
public PolyvTickSeekBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PolyvTickSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initStrokePaint();
fingerRadius = PolyvScreenUtils.dip2px(getContext(), 16);
}
//初始化画笔
private void initStrokePaint() {
mStockPaint = new Paint();
mStockPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mStockPaint.setAntiAlias(true);
}
private void initSeekBarInfo() {
height = getHeight();
width = getWidth();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
initSeekBarInfo();
drawTicks(canvas);
}
private void drawTicks(Canvas canvas) {
if (mTickDataList == null || mTickDataList.size() == 0) {
return;
}
for (int i = 0; i < mTickDataList.size(); i++) {
TickData tickData = mTickDataList.get(i);
if (tickData.progress > getMax() || tickData.progress < 0)
continue;
mStockPaint.setColor(tickData.color);
int thumbHeight = height - getPaddingTop() - getPaddingBottom();//thumb的高度
float cy = thumbHeight / 2.0f + getPaddingTop() - getPaddingBottom();//打点的圆心y轴位置
float percent = tickData.progress / (getMax() * 1.0f);
float cx = (width - getPaddingLeft() - getPaddingRight()) * percent;//打点的圆心x轴位置
cx += getPaddingLeft();
float radius = thumbHeight / 3.6f;//打点的半径
//圆心在端点上
// if (cx < getPaddingLeft())
// cx = getPaddingLeft();
// else if (cx > width - getPaddingRight())
// cx = width - getPaddingRight();
//圆心在端点内
if (cx < getPaddingLeft() + radius)
cx = getPaddingLeft() + radius;
else if (cx > width - getPaddingRight() - radius)
cx = width - getPaddingRight() - radius;
//调整两端的位置
if (cx + getPaddingLeft() < radius)
cx = radius - getPaddingLeft();
else if (width - cx + getPaddingRight() < radius)
cx = cx - (radius - (width - cx) - getPaddingRight());
tickData.cx = cx;//设置打点的圆心位置
canvas.drawCircle(cx, cy, radius, mStockPaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean hasTick = mTickDataList != null && mTickDataList.size() > 0;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
isMoved = false;
moveCount = 0;
return hasTick || super.onTouchEvent(event);
case MotionEvent.ACTION_MOVE:
if (++moveCount >= 7) {
isMoved = true;
} else {
return hasTick || super.onTouchEvent(event);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (!isMoved) {
float ex = event.getX();
//查找点击的附近是否有打点
int fitPosition;
if (hasTick && (fitPosition = halfSearch(mTickDataList, 0, mTickDataList.size() - 1, ex, fingerRadius)) != -1) {
if (onTickClickListener != null) {
onTickClickListener.onTickClick(mTickDataList.get(fitPosition));
}
} else {
if (onTickClickListener != null) {
boolean result = onTickClickListener.onSeekBarClick();
return hasTick ? (result ? super.onTouchEvent(event) : !result) : super.onTouchEvent(event);
}
}
return hasTick || super.onTouchEvent(event);
}
break;
}
return super.onTouchEvent(event);
}
private int findCloserPoint(List<TickData> tickDataList, int position, float ex, boolean isFirst, float fingerRadius) {
if (position < 0 || position > tickDataList.size() - 1) {
return -1;
}
float cxExDistance = Math.abs(tickDataList.get(position).cx - ex);
if (cxExDistance <= fingerRadius) {//满足条件,继续查找是否有离触摸点更近的打点
int preMiddle = position - 1;
int sufMiddle = position + 1;
int fitPosition = position;
if (preMiddle >= 0) {
float preCxExDistance = Math.abs(tickDataList.get(preMiddle).cx - ex);
if (preCxExDistance < cxExDistance) {
cxExDistance = preCxExDistance;
fitPosition = preMiddle;
}
}
if (sufMiddle <= tickDataList.size() - 1) {
float sufCxExDistance = Math.abs(tickDataList.get(sufMiddle).cx - ex);
if (sufCxExDistance <= cxExDistance) {//相同位置的两点,取后续的
fitPosition = sufMiddle;
}
}
if (fitPosition != position) {
return findCloserPoint(tickDataList, fitPosition, ex, false, fingerRadius);
}
} else if (isFirst) {//第一次查找不满足条件时返回-1
return -1;
}
return position;
}
private int halfSearch(List<TickData> tickDataList, int startPosition, int endPosition, float ex, float fingerRadius) {
if (tickDataList == null || tickDataList.size() == 0) {
return -1;
}
int length = tickDataList.size();
if (length < startPosition || length < endPosition || startPosition > endPosition)
return -1;
while (startPosition <= endPosition) {
int middle = startPosition + ((endPosition - startPosition) >> 1);//偶数middle取前面的
float cx = tickDataList.get(middle).cx;
int fitPosition;
if ((fitPosition = findCloserPoint(tickDataList, middle, ex, true, fingerRadius)) != -1) {
return fitPosition;
} else if (ex - cx > 0) {//中间点在触摸点的前面
startPosition = middle + 1;
} else {//中间点在触摸点的后面
endPosition = middle - 1;
}
}
return -1;
}
//设置时间点
public void setTicks(List<TickData> tickDataList) {
this.mTickDataList = tickDataList;
Collections.sort(this.mTickDataList, new Comparator<TickData>() {
@Override
public int compare(TickData lhs, TickData rhs) {
return (int) (lhs.progress - rhs.progress);//自然排序
}
});
invalidate();
}
public interface OnTickClickListener {
void onTickClick(TickData tickData);
/**
* @return 有打点数据时,点击非打点处抬起(中途没有移动),是否会触发onProgressChanged
*/
boolean onSeekBarClick();
}
private OnTickClickListener onTickClickListener;
public void setOnTickClickListener(OnTickClickListener l) {
this.onTickClickListener = l;
}
public static class TickData {
private int keyTime;
private float progress;
private int color;
private Object tag;
//位置
private float cx;
public TickData(float progress, int color, Object tag) {
this((int) progress, progress, color, tag);
}
public TickData(int keyTime, float progress, int color, Object tag) {
this.keyTime = keyTime;
this.progress = progress;
this.color = color;
this.tag = tag;
}
public int getKeyTime() {
return keyTime;
}
public void setKeyTime(int keyTime) {
this.keyTime = keyTime;
}
public float getProgress() {
return progress;
}
public void setProgress(float progress) {
this.progress = progress;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public Object getTag() {
return tag;
}
public void setTag(Object tag) {
this.tag = tag;
}
public float getCx() {
return cx;
}
public void setCx(float cx) {
this.cx = cx;
}
@Override
public String toString() {
return "TickData{" +
"keyTime=" + keyTime +
", progress=" + progress +
", color=" + color +
", tag=" + tag +
", cx=" + cx +
'}';
}
}
}
@@ -0,0 +1,149 @@
package cn.shangyu.gdxzExpert.polyvplayer.view;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.easefun.polyvsdk.vo.PolyvVideoVO;
import cn.shangyu.gdxzExpert.R;
public class PolyvTickTips extends FrameLayout {
private View view;
private TextView tv_time, tv_context;
private ImageView iv_seek;
private Runnable runnable;
private PolyvTickSeekBar.TickData tickData;
public PolyvTickTips(@NonNull Context context) {
this(context, null);
}
public PolyvTickTips(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public PolyvTickTips(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
view = LayoutInflater.from(context).inflate(R.layout.polyv_tick_tips, this);
initView();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
removeCallbacks(runnable);
}
private void initView() {
tv_time = (TextView) view.findViewById(R.id.tv_time);
tv_context = (TextView) view.findViewById(R.id.tv_context);
iv_seek = (ImageView) view.findViewById(R.id.iv_seek);
iv_seek.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onSeekClickListener != null)
onSeekClickListener.onSeekClick(tickData);
}
});
}
public void setOnSeekClickListener(OnSeekClickListener onSeekClickListener) {
this.onSeekClickListener = onSeekClickListener;
}
private OnSeekClickListener onSeekClickListener;
public interface OnSeekClickListener {
void onSeekClick(PolyvTickSeekBar.TickData tickData);
}
public boolean isShow() {
return getVisibility() == View.VISIBLE;
}
public void hide() {
removeCallbacks(runnable);
setVisibility(View.INVISIBLE);
}
private void handleData(final PolyvTickSeekBar.TickData tickData) {
if (tickData == null || !(tickData.getTag() instanceof PolyvVideoVO.Videokeyframe))
return;
MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
mlp.leftMargin = 0;
setLayoutParams(mlp);//先把margin调整为0,避免textview的文本由于上次的margin显示不全
PolyvVideoVO.Videokeyframe videokeyframe = (PolyvVideoVO.Videokeyframe) tickData.getTag();
StringBuilder stringBuilder = new StringBuilder();
if (videokeyframe.getHouts() > 9) {
stringBuilder.append(videokeyframe.getHouts()).append(":");
} else if (videokeyframe.getHouts() > 0) {
stringBuilder.append(0).append(videokeyframe.getHouts()).append(":");
}
if (videokeyframe.getMinutes() > 9) {
stringBuilder.append(videokeyframe.getMinutes()).append(":");
} else if (videokeyframe.getMinutes() >= 0) {
stringBuilder.append(0).append(videokeyframe.getMinutes()).append(":");
}
if (videokeyframe.getSeconds() > 9) {
stringBuilder.append(videokeyframe.getSeconds());
} else if (videokeyframe.getSeconds() >= 0) {
stringBuilder.append(0).append(videokeyframe.getSeconds());
}
tv_time.setText(stringBuilder.toString());
tv_context.setMaxWidth(((ViewGroup) getParent()).getWidth() * 3 / 4);//调整最大的宽度,不然iv_seek可能会显示不了
tv_context.setText(videokeyframe.getKeycontext());
tv_context.post(runnable = new Runnable() {
@Override
public void run() {
//调整位置
int parentWidth = ((ViewGroup) getParent()).getWidth();
int width = getWidth();
float cx = tickData.getCx();
MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
int halfWidth = width / 2;
if (cx > halfWidth) {
int leftMargin = (int) (cx - halfWidth);
if (leftMargin + width > parentWidth) {
if (cx > parentWidth / 2) {
leftMargin = parentWidth - width;
} else {
leftMargin = 0;//中间及左边都为0
}
}
mlp.leftMargin = leftMargin;
} else {
mlp.leftMargin = 0;
}
setLayoutParams(mlp);
setVisibility(View.VISIBLE);
}
});
}
public void show(final PolyvTickSeekBar.TickData tickData) {
if (this.tickData != tickData || !isShow()) {
this.tickData = tickData;
removeCallbacks(runnable);
if (isShow()) {
hide();
postDelayed(runnable = new Runnable() {
@Override
public void run() {
handleData(tickData);
}
}, 200);
} else {
handleData(tickData);
}
}
}
}