自定义view-仿网易音乐播放的按键
上图:
上代码:
package com.example.testview; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.animation.LinearInterpolator; import android.widget.LinearLayout; import android.widget.TextView; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.ViewCompat; @SuppressLint("NewApi") public class MusicView extends LinearLayout { public MusicView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); // TODO Auto-generated constructor stub } @TargetApi(Build.VERSION_CODES.HONEYCOMB) public MusicView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // TODO Auto-generated constructor stub } public MusicView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public MusicView(Context context) { super(context); // TODO Auto-generated constructor stub } private void initView(Context mContext) { this.setClickable(true); initLine(mContext); } int lineNum = 4; int lineW = 5; TextView[] lines; LinearLayout.LayoutParams[] lps; int lineBgNomal = 0xFF000000; int lineBgClick = 0xFFFFFFFF; private void initLine(Context mContext) { if (lines == null) { lines = new TextView[lineNum]; lps = new LinearLayout.LayoutParams[lineNum]; for (int i = 0; i < lines.length; i++) { lines[i] = new TextView(mContext); lines[i].setBackgroundColor(lineBgNomal); lps[i] = new LinearLayout.LayoutParams(lineW, LinearLayout.LayoutParams.MATCH_PARENT); lps[i].weight = 1; } } } private void setLineBg(int color) { if (lines != null) for (int i = 0; i < lines.length; i++) { lines[i].setBackgroundColor(color); } } int mHeight; int mWidth; boolean isNeedRelayoutLines = true; @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int height = getMeasuredHeight(); int width = getMeasuredWidth(); if (height > 0) { mHeight = height; } if (width > 0) { mWidth = width; } if (isNeedRelayoutLines && mWidth > 0 && isShowToWindow) { int lPad = getPaddingLeft(); int rPad = getPaddingRight(); float perW = (mWidth - (lPad + rPad)) * 1.0f / lineNum; int marginW = (int) ((perW - lineW) / 2); for (int i = 0; i < lines.length; i++) { lps[i].leftMargin = lps[i].rightMargin = marginW; addView(lines[i], lps[i]); } isNeedRelayoutLines = false; invalidate(); startMoveAnimation(); } } boolean isShowToWindow = false; @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); isShowToWindow = true; } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); isShowToWindow = false; isNeedRelayoutLines = true; } boolean isTouched = false; @Override public boolean onTouchEvent(MotionEvent event) { final int action = MotionEventCompat.getActionMasked(event); Log.e("mytag", "<<" + action); switch (action) { case MotionEvent.ACTION_DOWN: if (!isTouched) { isTouched = true; setLineBg(lineBgClick); invalidate(); } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: if (isTouched) { isTouched = false; setLineBg(lineBgNomal); invalidate(); } break; } return super.onTouchEvent(event); } // ---------------- // move // ---------------- private void setLineMovePercent(TextView line, float percent) { int topPad = getPaddingTop(); int bottomPad = getPaddingBottom(); int LineHeight = mHeight - (topPad + bottomPad); int margintop = (int) ((1 - percent) * LineHeight); LinearLayout.LayoutParams ll = (LayoutParams) line.getLayoutParams(); ll.topMargin = margintop; line.setLayoutParams(ll); } class LineMoveAnimationRunable implements Runnable { long startTime; long curTime; long durationTime; float startPercent; float toPercent; TextView line; MoveAnimationEndListener mMoveAnimationEndListener; public LineMoveAnimationRunable(TextView line, float startPercent, float toPercent, long durationTime, MoveAnimationEndListener mMoveAnimationEndListener) { this.durationTime = durationTime; this.startPercent = startPercent; this.toPercent = toPercent; this.line = line; this.mMoveAnimationEndListener = mMoveAnimationEndListener; } @Override public void run() { if (startTime == 0) { startTime = System.currentTimeMillis(); } curTime = System.currentTimeMillis(); float percent = (curTime - startTime) * 1.0f / durationTime; LinearInterpolator linearInterpolator = new LinearInterpolator(); float dpercent = linearInterpolator.getInterpolation(percent); float mpercent = startPercent; // Log.e("mytag", "<<" + dpercent); if (startPercent < toPercent) {// 增加 mpercent = startPercent + dpercent; if (mpercent >= toPercent) { mpercent = toPercent; } } else { mpercent = startPercent - dpercent; if (mpercent <= toPercent) { mpercent = toPercent; } } if (!isShowToWindow) { return; } setLineMovePercent(line, mpercent); invalidate(); if (mpercent == toPercent) { if (mMoveAnimationEndListener != null) { mMoveAnimationEndListener.AnimationEnd(line, startPercent, toPercent, durationTime); } return; } else { line.postDelayed(this, 50L); } } } interface MoveAnimationEndListener { public void AnimationEnd(TextView line, float startPercent, float toPercent, long durationTime); } public void startMoveAnimation() { // 0 2 0.5 -1 // 1 3 1-0.5 MoveAnimationEndListener listener = new MoveAnimationEndListener() { @Override public void AnimationEnd(TextView line, float startPercent, float toPercent, long durationTime) { LineMoveAnimationRunable moveRunnable = new LineMoveAnimationRunable( line, toPercent, startPercent, durationTime, this); line.postDelayed(moveRunnable, 200L); } }; for (int i = 0; i < lines.length; i++) { if (i % 2 == 0) { LineMoveAnimationRunable moveRunnable = new LineMoveAnimationRunable( lines[i], 0.5f, 1.0f, 1000L, listener); lines[i].post(moveRunnable); } else { LineMoveAnimationRunable moveRunnable = new LineMoveAnimationRunable( lines[i], 1f, 0.5f, 1000L, listener); lines[i].post(moveRunnable); } } } }
遗留问题:如上 每个line是单独的线程 可优化为四个line setMargin之后统一invalidate
自定义控件总结
几个相关函数:
1.构造函数: 初始化view ,获取自定义属性
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RefreshView); final int type = a.getInteger(R.styleable.RefreshView_type, STYLE_SUN);
2.onMeasure函数 中获取measure后的宽高
int height = getMeasuredHeight(); int width = getMeasuredWidth();
调用子view的 measure函数,
MeasureSpec.EXACTLY 父类指定大小
AT_MOST 子类尽可能大
UNSPECIFIED 未指定大小
widthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingRight() - getPaddingLeft(), MeasureSpec.EXACTLY); heightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY); mTarget.measure(widthMeasureSpec, heightMeasureSpec); mRefreshView.measure(widthMeasureSpec, heightMeasureSpec);
3.onLayout函数布局 调用 子view的layout 函数布局
protected void onLayout(boolean changed, int l, int t, int r, int b) { ... ... int height = getMeasuredHeight(); int width = getMeasuredWidth(); int left = getLeft(); int top = getTop(); int right = getRight(); int bottom = getBottom(); int lpad = getPaddingLeft(); int rpad = getPaddingRight(); int tpad = getPaddingTop(); int bpad = getPaddingBottom(); mTarget.layout(left + lpad, top + tpad+ mCurrentOffsetTop, right-rpad, bottom-bpad + mCurrentOffsetTop); mRefreshView.layout(left, top, left + width - right, top + height - bottom); }
4.onInterceptTouchEvent 拦截touch事件 主要做状态的改变检测
onTouchEvent 主要touch逻辑操作 计算滑动距离等 改变子类view 需调用 this.validate or this. postvalidate
改变自身需要父类控件重新布局需要调用 requireLayout
5.动画相关 如 拖拽完成后的动画复位 Animation类 或者 Runnable类
mAnimateToStartPosition.reset(); mAnimateToStartPosition.setDuration(animationDuration); mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator); mAnimateToStartPosition.setAnimationListener(mToStartListener); mRefreshView.clearAnimation(); mRefreshView.startAnimation(mAnimateToStartPosition);
private final Animation mAnimateToStartPosition = new Animation() { @Override public void applyTransformation(float interpolatedTime, Transformation t) { moveToStart(interpolatedTime); } };
interpolateTime 时间所对应的差值点