Android實現小米相機底部滑動指示器
近期工作內容需要涉及到相機開發,其中一個功能點就是實現一個相機預覽頁底部的滑動指示器,現在整理出來供大傢討論參考。
先上一張圖看下效果:
主要實現功能有:
1.支持左右滑動,每次滑動一個tab
2.支持tab點擊,直接跳到對應tab
3.選中的tab一直處於居中位置
4.支持部分UI自定義(大傢可根據需要自己改動)
5.tab點擊回調
6.內置Tab接口,放入的內容需要實現Tab接口
7.設置預選中tab
public class CameraIndicator extends LinearLayout { // 當前選中的位置索引 private int currentIndex; //tabs集合 private Tab[] tabs; // 利用Scroller類實現最終的滑動效果 public Scroller mScroller; //滑動執行時間(ms) private int mDuration = 300; //選中text的顏色 private int selectedTextColor = 0xffffffff; //未選中的text的顏色 private int normalTextColor = 0xffffffff; //選中的text的背景 private Drawable selectedTextBackgroundDrawable; private int selectedTextBackgroundColor; private int selectedTextBackgroundResources; //是否正在滑動 private boolean isScrolling = false; private int onLayoutCount = 0; public CameraIndicator(Context context) { this(context, null); } public CameraIndicator(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public CameraIndicator(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mScroller = new Scroller(context); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); //測量所有子元素 measureChildren(widthMeasureSpec, heightMeasureSpec); //處理wrap_content的情況 int width = 0; int height = 0; if (getChildCount() == 0) { setMeasuredDimension(0, 0); } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); width += child.getMeasuredWidth(); height = Math.max(height, child.getMeasuredHeight()); } setMeasuredDimension(width, height); } else if (widthMode == MeasureSpec.AT_MOST) { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); width += child.getMeasuredWidth(); } setMeasuredDimension(width, heightSize); } else if (heightMode == MeasureSpec.AT_MOST) { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); height = Math.max(height, child.getMeasuredHeight()); } setMeasuredDimension(widthSize, height); } else { //如果自定義ViewGroup之初就已確認該ViewGroup寬高都是match_parent,那麼直接設置即可 setMeasuredDimension(widthSize, heightSize); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { //給選中text的添加背景會多次進入onLayout,會導致位置有問題,暫未解決 if (onLayoutCount > 0) { return; } onLayoutCount++; int counts = getChildCount(); int childLeft = 0; int childRight = 0; int childTop = 0; int childBottom = 0; //居中顯示 int widthOffset = 0; //計算最左邊的子view距離中心的距離 for (int i = 0; i < currentIndex; i++) { View childView = getChildAt(i); widthOffset += childView.getMeasuredWidth() + getMargins(childView).get(0)+getMargins(childView).get(2); } //計算出每個子view的位置 for (int i = 0; i < counts; i++) { View childView = getChildAt(i); childView.setOnClickListener(v -> moveTo(v)); if (i != 0) { View preView = getChildAt(i - 1); childLeft = preView.getRight() +getMargins(preView).get(2)+ getMargins(childView).get(0); } else { childLeft = (getWidth() - getChildAt(currentIndex).getMeasuredWidth()) / 2 - widthOffset; } childRight = childLeft + childView.getMeasuredWidth(); childTop = (getHeight() - childView.getMeasuredHeight()) / 2; childBottom = (getHeight() + childView.getMeasuredHeight()) / 2; childView.layout(childLeft, childTop, childRight, childBottom); } TextView indexText = (TextView) getChildAt(currentIndex); changeSelectedUIState(indexText); } private List<Integer> getMargins(View view) { LayoutParams params = (LayoutParams) view.getLayoutParams(); List<Integer> listMargin = new ArrayList<Integer>(); listMargin.add(params.leftMargin); listMargin.add(params.topMargin); listMargin.add(params.rightMargin); listMargin.add(params.bottomMargin); return listMargin; } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { // 滑動未結束,內部使用scrollTo方法完成實際滑動 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); invalidate(); } else { //滑動完成 isScrolling = false; if (listener != null) { listener.onChange(currentIndex,tabs[currentIndex]); } } super.computeScroll(); } /** * 改變選中TextView的顏色 * * @param currentIndex 滑動之前選中的那個 * @param nextIndex 滑動之後選中的那個 */ public final void scrollToNext(int currentIndex, int nextIndex) { TextView selectedText = (TextView) getChildAt(currentIndex); if (selectedText != null) { selectedText.setTextColor(normalTextColor); selectedText.setBackground(null); } selectedText = (TextView) getChildAt(nextIndex); if (selectedText != null) { changeSelectedUIState(selectedText); } } private void changeSelectedUIState(TextView view) { view.setTextColor(selectedTextColor); if (selectedTextBackgroundDrawable != null) { view.setBackground(selectedTextBackgroundDrawable); } if (selectedTextBackgroundColor != 0) { view.setBackgroundColor(selectedTextBackgroundColor); } if (selectedTextBackgroundResources != 0) { view.setBackgroundResource(selectedTextBackgroundResources); } } /** * 向右滑一個 */ public void moveToRight() { moveTo(getChildAt(currentIndex - 1)); } /** * 向左滑一個 */ public void moveToLeft() { moveTo(getChildAt(currentIndex + 1)); } /** * 滑到目標view * * @param view 目標view */ private void moveTo(View view) { for (int i = 0; i < getChildCount(); i++) { if (view == getChildAt(i)) { if (i == currentIndex) { //不移動 break; } else if (i < currentIndex) { //向右移 if (isScrolling) { return; } isScrolling = true; int dx = getChildAt(currentIndex).getLeft() - view.getLeft() + (getChildAt(currentIndex).getMeasuredWidth() - view.getMeasuredWidth()) / 2; //這裡使用scroll會使滑動更平滑不卡頓,scroll會根據起點、終點及時間計算出每次滑動的距離,其內部有一個插值器 mScroller.startScroll(getScrollX(), 0, -dx, 0, mDuration); scrollToNext(currentIndex, i); setCurrentIndex(i); invalidate(); } else if (i > currentIndex) { //向左移 if (isScrolling) { return; } isScrolling = true; int dx = view.getLeft() - getChildAt(currentIndex).getLeft() + (view.getMeasuredWidth() - getChildAt(currentIndex).getMeasuredWidth()) / 2; mScroller.startScroll(getScrollX(), 0, dx, 0, mDuration); scrollToNext(currentIndex, i); setCurrentIndex(i); invalidate(); } } } } /** * 設置tabs * * @param tabs */ public void setTabs(Tab... tabs) { this.tabs = tabs; //暫時不通過layout佈局添加textview if (getChildCount()>0){ removeAllViews(); } for (Tab tab : tabs) { TextView textView = new TextView(getContext()); textView.setText(tab.getText()); textView.setTextSize(14); textView.setTextColor(selectedTextColor); textView.setPadding(dp2px(getContext(),5), dp2px(getContext(),2), dp2px(getContext(),5),dp2px(getContext(),2)); LayoutParams layoutParams= new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT); layoutParams.rightMargin=dp2px(getContext(),2.5f); layoutParams.leftMargin=dp2px(getContext(),2.5f); textView.setLayoutParams(layoutParams); addView(textView); } } public int getCurrentIndex() { return currentIndex; } //設置默認選中第幾個 public void setCurrentIndex(int currentIndex) { this.currentIndex = currentIndex; } //設置滑動時間 public void setDuration(int mDuration) { this.mDuration = mDuration; } public void setSelectedTextColor(int selectedTextColor) { this.selectedTextColor = selectedTextColor; } public void setNormalTextColor(int normalTextColor) { this.normalTextColor = normalTextColor; } public void setSelectedTextBackgroundDrawable(Drawable selectedTextBackgroundDrawable) { this.selectedTextBackgroundDrawable = selectedTextBackgroundDrawable; } public void setSelectedTextBackgroundColor(int selectedTextBackgroundColor) { this.selectedTextBackgroundColor = selectedTextBackgroundColor; } public void setSelectedTextBackgroundResources(int selectedTextBackgroundResources) { this.selectedTextBackgroundResources = selectedTextBackgroundResources; } public interface OnSelectedChangedListener { void onChange(int index, Tab tag); } private OnSelectedChangedListener listener; public void setOnSelectedChangedListener(OnSelectedChangedListener listener) { if (listener != null) { this.listener = listener; } } private int dp2px(Context context, float dpValue) { DisplayMetrics metrics = context.getResources().getDisplayMetrics(); return (int) (metrics.density * dpValue + 0.5F); } public interface Tab{ String getText(); } private float startX = 0f; @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { startX = event.getX(); } if (event.getAction() == MotionEvent.ACTION_UP) { float endX = event.getX(); //向左滑條件 if (endX - startX > 50 && currentIndex > 0) { moveToRight(); } if (startX - endX > 50 && currentIndex < getChildCount() - 1) { moveToLeft(); } } return true; } @Override public boolean onInterceptTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { startX = event.getX(); } if (event.getAction() == MotionEvent.ACTION_UP) { float endX = event.getX(); //向左滑條件 if (Math.abs(startX-endX)>50){ onTouchEvent(event); } } return super.onInterceptTouchEvent(event); } }
在Activity或fragment中使用
private var tabs = listOf("慢動作", "短視頻", "錄像", "拍照", "108M", "人像", "夜景", "萌拍", "全景", "專業") lateinit var imageAnalysis:ImageAnalysis override fun initView() { //實現瞭CameraIndicator.Tab的對象 val map = tabs.map { CameraIndicator.Tab { it } }?.toTypedArray() ?: arrayOf() //將tab集合設置給cameraIndicator,(binding.cameraIndicator即xml佈局裡的控件) binding.cameraIndicator.setTabs(*map) //默認選中 拍照 binding.cameraIndicator.currentIndex = 3 //點擊某個tab的回調 binding.cameraIndicator.setSelectedTextBackgroundResources(R.drawable.selected_text_bg) binding.cameraIndicator.setOnSelectedChangedListener { index, tag -> Toast.makeText(this,tag.text,Toast.LENGTH_SHORT).show() } }
以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- None Found