我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程网。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

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

这篇文章给大家分享的是有关Android怎么实现小米相机底部滑动指示器的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。先上一张图看下效果:主要实现功能有:1.支持左右滑动,每次滑动一个tab2.支持tab点击,直接
2023-06-14

Android ViewPager无限循环实现底部小圆点动态滑动

页面拖动到最后一页 再向下滑动回复到 第一页,第一页向前滑动回到 最后一页 同时,底部红色小圆点随着页面的滑动距离比例随时改变位置布局:
2022-06-06

Android实现局部图片滑动指引效果示例

今天发布本文的原因是应一个网友要求,就是实现局部的图片滑动指引效果。这种效果一般是在新闻客户端上比较常见,其功能是:1、顶部单张图片左右拖拉滑动;2、带指引;3、仅滑动顶部单张图片,不滑动页面,下面的图文内容不动;4、类似于新闻客户端的功能
2022-06-06

Android中使用ScrollView实现滑动到底部显示加载更多

这是效果主要是onTouchListener监听事件,监视什么时候滑到底部 同时要理解getMeasuredHeight和getHeight的区别 getMeasuredHeight:全部的长度 包括隐藏的 getHeight:在布局中展示
2022-06-06

Android仿天猫横向滑动指示器功能的实现

这篇文章主要介绍了Android仿天猫横向滑动指示器,Android开发中会有很多很新奇的交互,比如天猫商城的首页头部的分类,使用的是GridLayoutManager+横向指示器实现的,需要的朋友可以参考下
2022-11-13

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录