Android高仿iOS Messages录音操作按钮
目录
前面的2次开发,分别完成了实现录音和播放以及相对应的波形。
这次的开发目标是解决录音操作按钮。
一、目标
实现录音操作按钮,为神马笔记实现录音功能做准备。
二、功能分析
iOS Messages录音按钮可以通过上下滑动选择不同的功能。
其中一个使用场景:
- 长按录音按钮启动录音功能
- 向上滑动到发送按钮,发送语音
- 向下滑动到停止按钮,停止录音
- 滑动到按钮外部,不进行任何操作
截图 | 描述 |
---|---|
录音按钮及按下状态 | |
发送按钮及按下状态 | |
播放按钮及按下状态 | |
暂停按钮及按下状态 | |
三、实现效果
没有动态效果,看起来跟LinearLayout
或者其他的控件容器毫无差别。
进行触控操作,则可以通过上下滑动选择不同的功能。
四、实现过程
因为布局方式与LinearLayout
非常一致,因此选择LinearLayout
为蓝本,然后重新编写触控事件。
触控事件的处理方式则参考ScrollView
,因为无需处理滚动,实现非常之简单。
使用时,需要将子控件clickable
属性设置为true
,才能反应控件状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
public class ActionLayout extends LinearLayout { private static final String TAG = "ActionLayout"; View actionView; View.OnClickListener onClickListener; /** * Sentinel value for no current active pointer. * Used by {@link #mActivePointerId}. */ private static final int INVALID_POINTER = -1; /** * ID of the active pointer. This is used to retain consistency during * drags/flings if multiple pointers are used. */ private int mActivePointerId = INVALID_POINTER; public ActionLayout(Context context) { this(context, null); } public ActionLayout(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public ActionLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public ActionLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override public void setOnClickListener(@Nullable OnClickListener l) { this.onClickListener = l; super.setOnClickListener(l); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { super.onInterceptTouchEvent(ev); final int action = ev.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { mActivePointerId = ev.getPointerId(0); this.updateAction(ev); break; } case MotionEvent.ACTION_MOVE: { if (mActivePointerId == INVALID_POINTER) { mActivePointerId = ev.getPointerId(0); } this.updateAction(ev); break; } case MotionEvent.ACTION_CANCEL: { this.mActivePointerId = INVALID_POINTER; this.actionView = null; break; } case MotionEvent.ACTION_UP: { this.fireAction(); this.mActivePointerId = INVALID_POINTER; this.actionView = null; break; } case MotionEvent.ACTION_POINTER_UP: { onSecondaryPointerUp(ev); this.updateAction(ev); break; } } return isClickable() || (getChildCount() != 0); } @Override public boolean onTouchEvent(MotionEvent ev) { final int actionMasked = ev.getActionMasked(); switch (actionMasked) { case MotionEvent.ACTION_DOWN: { mActivePointerId = ev.getPointerId(0); updateAction(ev); break; } case MotionEvent.ACTION_MOVE: { if (mActivePointerId == INVALID_POINTER) { mActivePointerId = ev.getPointerId(0); } updateAction(ev); break; } case MotionEvent.ACTION_UP: { fireAction(); this.mActivePointerId = INVALID_POINTER; this.actionView = null; break; } case MotionEvent.ACTION_CANCEL: { mActivePointerId = INVALID_POINTER; this.actionView = null; break; } case MotionEvent.ACTION_POINTER_DOWN: { final int index = ev.getActionIndex(); this.mActivePointerId = ev.getPointerId(index); this.updateAction(ev); break; } case MotionEvent.ACTION_POINTER_UP: { onSecondaryPointerUp(ev); this.updateAction(ev); break; } } return isClickable() || (getChildCount() != 0); } void fireAction() { if (actionView == null) { return; } actionView.setPressed(false); if (onClickListener != null) { onClickListener.onClick(actionView); } } void updateAction(MotionEvent ev) { final int activePointerIndex = ev.findPointerIndex(mActivePointerId); if (activePointerIndex == -1) { Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); return; } final float x = ev.getX(activePointerIndex); final float y = ev.getY(activePointerIndex); View view = findChildViewUnder(x, y); if (view != null && view.getVisibility() != View.VISIBLE) { view = null; } if (actionView == view) { return; } if (actionView != null) { actionView.setPressed(false); } if (view != null) { view.setPressed(true); } this.actionView = view; } View findChildViewUnder(float x, float y) { final int count = this.getChildCount(); for (int i = count - 1; i >= 0; i--) { View child = this.getChildAt(i); final float translationX = child.getTranslationX(); final float translationY = child.getTranslationY(); if (x >= child.getLeft() + translationX && x <= child.getRight() + translationX && y >= child.getTop() + translationY && y <= child.getBottom() + translationY) { return child; } } return null; } void onSecondaryPointerUp(MotionEvent ev) { final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; final int pointerId = ev.getPointerId(pointerIndex); if (pointerId == mActivePointerId) { // This was our active pointer going up. Choose a new // active pointer and adjust accordingly. // TODO: Make this decision more intelligent. final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mActivePointerId = ev.getPointerId(newPointerIndex); } } } |
五、开发过程回顾
明确开发目标,选择LinearLayout
和ScrollView
为蓝本进行改造。
还从RecyclerView
拿了点代码来用。
六、接下来
继续朝录音编辑器迈进。
七、Finally
尔时。世尊而说偈言。
若以色见我。 以音声求我。 是人行邪道。 不能见如来。