Android实现小米相机底部滑动指示器

作者:奋斗的小鹰 时间:2023-03-12 04:57:00 

近期工作内容需要涉及到相机开发,其中一个功能点就是实现一个相机预览页底部的滑动指示器,现在整理出来供大家讨论参考。

先上一张图看下效果:

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()
       }

}

来源:https://blog.csdn.net/qq_27246079/article/details/115321970

标签:Android,相机,指示器
0
投稿

猜你喜欢

  • MyBatis全局配置文件详解

    2021-09-19 10:13:32
  • C#实现带百分比的进度条功能示例

    2023-09-08 14:17:03
  • Java内存缓存工具Guava LoadingCache使用解析

    2023-05-25 08:36:36
  • Java制作智能拼图游戏原理及代码

    2022-08-02 21:46:57
  • Java一维数组和二维数组元素默认初始化值的判断方式

    2022-03-08 21:17:49
  • 浅谈Mybatis之参数传递的几种姿势

    2021-06-20 01:26:54
  • 浅谈Android中多线程切换的几种方法

    2022-03-12 06:03:17
  • Java设计模式之GOF23全面讲解

    2023-06-24 06:15:06
  • 最新IntelliJ IDEA2017.3 激活方式

    2023-08-12 00:47:24
  • 在WinForm应用程序中快速实现多语言的处理的方法

    2023-07-26 10:39:55
  • 利用Thumbnailator轻松实现图片缩放、旋转与加水印

    2022-03-26 18:44:04
  • mybatis sum(参数) 列名作为参数的问题

    2022-06-16 01:45:44
  • Java解决计算相邻两个数的最大差值的问题

    2022-03-29 05:47:20
  • 深入理解C#中foreach遍历的使用方法

    2023-11-02 08:51:04
  • 使用Files.walkFileTree遍历目录文件

    2021-09-27 06:12:40
  • Android解决所有双击优化的问题

    2023-07-27 09:07:03
  • SpringBoot使用Maven插件进行项目打包的方法

    2022-12-21 21:07:34
  • Android应用开发SharedPreferences存储数据的使用方法

    2023-06-23 16:02:47
  • C#获取上个月第一天和最后一天日期的方法

    2023-02-22 07:33:44
  • Spring底层事务原理解析

    2021-11-25 05:40:29
  • asp之家 软件编程 m.aspxhome.com