QQ登录

只需要一步,快速开始

APP扫码登录

只需要一步,快速开始

手机号码,快捷登录

泡泡马甲APP 更多内容请下载泡泡马甲手机客户端APP 立即下载 ×
查看: 2719|回复: 0

[Android/IOS] Android实现小米相机底部滑动指示器

[复制链接]

等级头衔

积分成就    金币 : 2806
   泡泡 : 1516
   精华 : 6
   在线时间 : 1244 小时
   最后登录 : 2024-5-5

丰功伟绩

优秀达人突出贡献荣誉管理论坛元老

联系方式
发表于 2021-4-14 16:39:35 | 显示全部楼层 |阅读模式
先上一张图看下效果:: s; r- `' V% x$ b0 j. z( p
1.jpg
4 [; x6 L, M& P1 f0 ]: e主要实现功能有:
' I' i& U6 _. |  j# o  1.支持左右滑动,每次滑动一个tab' ^4 U2 u5 ^, a/ S4 I% }
  2.支持tab点击,直接跳到对应tab6 H3 k; ]: ~, ~/ A
  3.选中的tab一直处于居中位置( V8 x4 h7 S# G7 o& W
  4.支持部分UI自定义(大家可根据需要自己改动); G0 ^+ ?2 u1 R3 \9 A. o
  5.tab点击回调
9 G" b7 |, {6 p" s" K  6.内置Tab接口,放入的内容需要实现Tab接口. {# V0 l9 r( h' H( D6 I0 i
  7.设置预选中tab8 l6 k' L2 t) S3 ]
  1. public class CameraIndicator extends LinearLayout {
  2.     // 当前选中的位置索引
  3.     private int currentIndex;
  4.     //tabs集合
  5.     private Tab[] tabs;
  6.   
  7.     // 利用Scroller类实现最终的滑动效果
  8.     public Scroller mScroller;
  9.     //滑动执行时间(ms)
  10.     private int mDuration = 300;
  11.     //选中text的颜色
  12.     private int selectedTextColor = 0xffffffff;
  13.     //未选中的text的颜色
  14.     private int normalTextColor = 0xffffffff;
  15.     //选中的text的背景
  16.     private Drawable selectedTextBackgroundDrawable;
  17.     private int selectedTextBackgroundColor;
  18.     private int selectedTextBackgroundResources;
  19.     //是否正在滑动
  20.     private boolean isScrolling = false;
  21.   
  22.     private int onLayoutCount = 0;
  23.   
  24.   
  25.     public CameraIndicator(Context context) {
  26.         this(context, null);
  27.     }
  28.   
  29.     public CameraIndicator(Context context, @Nullable AttributeSet attrs) {
  30.         this(context, attrs, 0);
  31.     }
  32.   
  33.     public CameraIndicator(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
  34.         super(context, attrs, defStyleAttr);
  35.         mScroller = new Scroller(context);
  36.   
  37.     }
  38.   
  39.     @Override
  40.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  41.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  42.         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  43.         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  44.         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  45.         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  46.         //测量所有子元素
  47.         measureChildren(widthMeasureSpec, heightMeasureSpec);
  48.         //处理wrap_content的情况
  49.         int width = 0;
  50.         int height = 0;
  51.         if (getChildCount() == 0) {
  52.             setMeasuredDimension(0, 0);
  53.         } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
  54.             for (int i = 0; i < getChildCount(); i++) {
  55.                 View child = getChildAt(i);
  56.                 width +=  child.getMeasuredWidth();
  57.                 height = Math.max(height, child.getMeasuredHeight());
  58.             }
  59.             setMeasuredDimension(width, height);
  60.         } else if (widthMode == MeasureSpec.AT_MOST) {
  61.             for (int i = 0; i < getChildCount(); i++) {
  62.                 View child = getChildAt(i);
  63.                 width +=  child.getMeasuredWidth();
  64.             }
  65.             setMeasuredDimension(width, heightSize);
  66.         } else if (heightMode == MeasureSpec.AT_MOST) {
  67.             for (int i = 0; i < getChildCount(); i++) {
  68.                 View child = getChildAt(i);
  69.                 height = Math.max(height, child.getMeasuredHeight());
  70.             }
  71.             setMeasuredDimension(widthSize, height);
  72.         } else {
  73.             //如果自定义ViewGroup之初就已确认该ViewGroup宽高都是match_parent,那么直接设置即可
  74.             setMeasuredDimension(widthSize, heightSize);
  75.         }
  76.     }
  77.   
  78.     @Override
  79.     protected void onLayout(boolean changed, int l, int t, int r, int b) {
  80.         //给选中text的添加背景会多次进入onLayout,会导致位置有问题,暂未解决
  81.         if (onLayoutCount > 0) {
  82.             return;
  83.         }
  84.         onLayoutCount++;
  85.   
  86.         int counts = getChildCount();
  87.         int childLeft = 0;
  88.         int childRight = 0;
  89.         int childTop = 0;
  90.         int childBottom = 0;
  91.         //居中显示
  92.         int widthOffset = 0;
  93.   
  94.   
  95.         //计算最左边的子view距离中心的距离
  96.         for (int i = 0; i < currentIndex; i++) {
  97.             View childView = getChildAt(i);
  98.             widthOffset += childView.getMeasuredWidth() + getMargins(childView).get(0)+getMargins(childView).get(2);
  99.         }
  100.   
  101.         //计算出每个子view的位置
  102.         for (int i = 0; i < counts; i++) {
  103.             View childView = getChildAt(i);
  104.             childView.setOnClickListener(v -> moveTo(v));
  105.             if (i != 0) {
  106.                 View preView = getChildAt(i - 1);
  107.                 childLeft = preView.getRight() +getMargins(preView).get(2)+ getMargins(childView).get(0);
  108.             } else {
  109.                 childLeft = (getWidth() - getChildAt(currentIndex).getMeasuredWidth()) / 2 - widthOffset;
  110.             }
  111.             childRight = childLeft + childView.getMeasuredWidth();
  112.             childTop = (getHeight() - childView.getMeasuredHeight()) / 2;
  113.             childBottom = (getHeight() + childView.getMeasuredHeight()) / 2;
  114.             childView.layout(childLeft, childTop, childRight, childBottom);
  115.         }
  116.   
  117.         TextView indexText = (TextView) getChildAt(currentIndex);
  118.         changeSelectedUIState(indexText);
  119.   
  120.     }
  121.   
  122.     private List<Integer> getMargins(View view) {
  123.         LayoutParams params = (LayoutParams) view.getLayoutParams();
  124.         List<Integer> listMargin = new ArrayList<Integer>();
  125.         listMargin.add(params.leftMargin);
  126.         listMargin.add(params.topMargin);
  127.         listMargin.add(params.rightMargin);
  128.         listMargin.add(params.bottomMargin);
  129.         return listMargin;
  130.     }
  131.   
  132.     @Override
  133.     public void computeScroll() {
  134.         if (mScroller.computeScrollOffset()) {
  135.             // 滑动未结束,内部使用scrollTo方法完成实际滑动
  136.             scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
  137.             invalidate();
  138.         } else {
  139.             //滑动完成
  140.             isScrolling = false;
  141.             if (listener != null) {
  142.                 listener.onChange(currentIndex,tabs[currentIndex]);
  143.             }
  144.         }
  145.         super.computeScroll();
  146.     }
  147.   
  148.   
  149.     /**
  150.      * 改变选中TextView的颜色
  151.      *
  152.      * @param currentIndex 滑动之前选中的那个
  153.      * @param nextIndex    滑动之后选中的那个
  154.      */
  155.     public final void scrollToNext(int currentIndex, int nextIndex) {
  156.         TextView selectedText = (TextView) getChildAt(currentIndex);
  157.         if (selectedText != null) {
  158.             selectedText.setTextColor(normalTextColor);
  159.             selectedText.setBackground(null);
  160.         }
  161.         selectedText = (TextView) getChildAt(nextIndex);
  162.         if (selectedText != null) {
  163.             changeSelectedUIState(selectedText);
  164.         }
  165.     }
  166.   
  167.     private void changeSelectedUIState(TextView view) {
  168.         view.setTextColor(selectedTextColor);
  169.         if (selectedTextBackgroundDrawable != null) {
  170.             view.setBackground(selectedTextBackgroundDrawable);
  171.         }
  172.   
  173.         if (selectedTextBackgroundColor != 0) {
  174.             view.setBackgroundColor(selectedTextBackgroundColor);
  175.         }
  176.         if (selectedTextBackgroundResources != 0) {
  177.             view.setBackgroundResource(selectedTextBackgroundResources);
  178.         }
  179.     }
  180.   
  181.   
  182.     /**
  183.      * 向右滑一个
  184.      */
  185.     public void moveToRight() {
  186.         moveTo(getChildAt(currentIndex - 1));
  187.     }
  188.   
  189.   
  190.     /**
  191.      * 向左滑一个
  192.      */
  193.     public void moveToLeft() {
  194.         moveTo(getChildAt(currentIndex + 1));
  195.     }
  196.   
  197.     /**
  198.      * 滑到目标view
  199.      *
  200.      * @param view 目标view
  201.      */
  202.     private void moveTo(View view) {
  203.         for (int i = 0; i < getChildCount(); i++) {
  204.             if (view == getChildAt(i)) {
  205.                 if (i == currentIndex) {
  206.                     //不移动
  207.                     break;
  208.                 } else if (i < currentIndex) {
  209.                     //向右移
  210.                     if (isScrolling) {
  211.                         return;
  212.                     }
  213.                     isScrolling = true;
  214.                     int dx = getChildAt(currentIndex).getLeft() - view.getLeft() + (getChildAt(currentIndex).getMeasuredWidth() - view.getMeasuredWidth()) / 2;
  215.                     //这里使用scroll会使滑动更平滑不卡顿,scroll会根据起点、终点及时间计算出每次滑动的距离,其内部有一个插值器
  216.                     mScroller.startScroll(getScrollX(), 0, -dx, 0, mDuration);
  217.                     scrollToNext(currentIndex, i);
  218.                     setCurrentIndex(i);
  219.                     invalidate();
  220.                 } else if (i > currentIndex) {
  221.                     //向左移
  222.                     if (isScrolling) {
  223.                         return;
  224.                     }
  225.                     isScrolling = true;
  226.                     int dx = view.getLeft() - getChildAt(currentIndex).getLeft() + (view.getMeasuredWidth() - getChildAt(currentIndex).getMeasuredWidth()) / 2;
  227.                     mScroller.startScroll(getScrollX(), 0, dx, 0, mDuration);
  228.                     scrollToNext(currentIndex, i);
  229.                     setCurrentIndex(i);
  230.                     invalidate();
  231.                 }
  232.             }
  233.         }
  234.     }
  235.   
  236.   
  237.     /**
  238.      * 设置tabs
  239.      *
  240.      * @param tabs
  241.      */
  242.     public void setTabs(Tab... tabs) {
  243.         this.tabs = tabs;
  244.         //暂时不通过layout布局添加textview
  245.         if (getChildCount()>0){
  246.             removeAllViews();
  247.         }
  248.         for (Tab tab : tabs) {
  249.             TextView textView = new TextView(getContext());
  250.             textView.setText(tab.getText());
  251.             textView.setTextSize(14);
  252.             textView.setTextColor(selectedTextColor);
  253.             textView.setPadding(dp2px(getContext(),5), dp2px(getContext(),2), dp2px(getContext(),5),dp2px(getContext(),2));
  254.             LayoutParams layoutParams= new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
  255.             layoutParams.rightMargin=dp2px(getContext(),2.5f);
  256.             layoutParams.leftMargin=dp2px(getContext(),2.5f);
  257.             textView.setLayoutParams(layoutParams);
  258.             addView(textView);
  259.         }
  260.     }
  261.   
  262.   
  263.     public int getCurrentIndex() {
  264.         return currentIndex;
  265.     }
  266.   
  267.     //设置默认选中第几个
  268.     public void setCurrentIndex(int currentIndex) {
  269.         this.currentIndex = currentIndex;
  270.     }
  271.   
  272.     //设置滑动时间
  273.     public void setDuration(int mDuration) {
  274.         this.mDuration = mDuration;
  275.     }
  276.   
  277.     public void setSelectedTextColor(int selectedTextColor) {
  278.         this.selectedTextColor = selectedTextColor;
  279.     }
  280.   
  281.     public void setNormalTextColor(int normalTextColor) {
  282.         this.normalTextColor = normalTextColor;
  283.     }
  284.   
  285.     public void setSelectedTextBackgroundDrawable(Drawable selectedTextBackgroundDrawable) {
  286.         this.selectedTextBackgroundDrawable = selectedTextBackgroundDrawable;
  287.     }
  288.   
  289.     public void setSelectedTextBackgroundColor(int selectedTextBackgroundColor) {
  290.         this.selectedTextBackgroundColor = selectedTextBackgroundColor;
  291.     }
  292.   
  293.     public void setSelectedTextBackgroundResources(int selectedTextBackgroundResources) {
  294.         this.selectedTextBackgroundResources = selectedTextBackgroundResources;
  295.     }
  296.   
  297.     public interface OnSelectedChangedListener {
  298.         void onChange(int index, Tab tag);
  299.     }
  300.   
  301.     private OnSelectedChangedListener listener;
  302.   
  303.     public void setOnSelectedChangedListener(OnSelectedChangedListener listener) {
  304.         if (listener != null) {
  305.             this.listener = listener;
  306.         }
  307.     }
  308.   
  309.     private int dp2px(Context context, float dpValue) {
  310.         DisplayMetrics metrics = context.getResources().getDisplayMetrics();
  311.         return (int) (metrics.density * dpValue + 0.5F);
  312.     }
  313.   
  314.   
  315.     public interface Tab{
  316.         String getText();
  317.     }
  318.   
  319.     private float startX = 0f;
  320.     @Override
  321.     public boolean onTouchEvent(MotionEvent event) {
  322.         if (event.getAction() == MotionEvent.ACTION_DOWN) {
  323.             startX = event.getX();
  324.         }
  325.         if (event.getAction() == MotionEvent.ACTION_UP) {
  326.             float endX = event.getX();
  327.             //向左滑条件
  328.             if (endX - startX > 50 && currentIndex > 0) {
  329.                 moveToRight();
  330.             }
  331.             if (startX - endX > 50 && currentIndex < getChildCount() - 1) {
  332.                 moveToLeft();
  333.             }
  334.         }
  335.         return true;
  336.     }
  337.   
  338.     @Override
  339.     public boolean onInterceptTouchEvent(MotionEvent event) {
  340.         if (event.getAction() == MotionEvent.ACTION_DOWN) {
  341.             startX = event.getX();
  342.         }
  343.         if (event.getAction() == MotionEvent.ACTION_UP) {
  344.             float endX = event.getX();
  345.             //向左滑条件
  346.             if (Math.abs(startX-endX)>50){
  347.                 onTouchEvent(event);
  348.             }
  349.         }
  350.         return super.onInterceptTouchEvent(event);
  351.     }
  352. }
在Activity或fragment中使用:
+ F/ \) Q8 W% b6 a) U1 C; i0 s
  1. private var tabs = listOf("慢动作", "短视频", "录像", "拍照", "108M", "人像", "夜景", "萌拍", "全景", "专业")
  2.     lateinit var  imageAnalysis:ImageAnalysis
  3.     override fun initView() {
  4.         //实现了CameraIndicator.Tab的对象
  5.         val map = tabs.map {
  6.             CameraIndicator.Tab { it }
  7.         }?.toTypedArray() ?: arrayOf()
  8.         //将tab集合设置给cameraIndicator,(binding.cameraIndicator即xml布局里的控件)
  9.         binding.cameraIndicator.setTabs(*map)
  10.         //默认选中  拍照
  11.         binding.cameraIndicator.currentIndex = 3
  12.         
  13. //点击某个tab的回调
  14. binding.cameraIndicator.setSelectedTextBackgroundResources(R.drawable.selected_text_bg)
  15.         binding.cameraIndicator.setOnSelectedChangedListener { index, tag ->
  16.             Toast.makeText(this,tag.text,Toast.LENGTH_SHORT).show()
  17.         }
  18. }
$ y' M+ \* @- a! u$ }& s
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|paopaomj.COM ( 渝ICP备18007172号 )

GMT+8, 2024-5-14 22:14

Powered by paopaomj X3.4 © 2016-2024 sitemap

快速回复 返回顶部 返回列表