33
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
+156
@@ -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);
|
||||
}
|
||||
}
|
||||
+41
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user