欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-06-28 15:13:57
近期工作内容需要涉及到相机开发,其中一个功能点就是实现一个相机预览页底部的滑动指示器,现在整理出来供大家讨论参考。先上一张图看下效果:主要实现功能有:1.支持左右滑动,每次滑动一个tab2.支持tab...

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

先上一张图看下效果:

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

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