Android实现小米相机底部滑动指示器
程序员文章站
2022-03-23 14:32:37
近期工作内容需要涉及到相机开发,其中一个功能点就是实现一个相机预览页底部的滑动指示器,现在整理出来供大家讨论参考。先上一张图看下效果:主要实现功能有:1.支持左右滑动,每次滑动一个tab2.支持tab...
近期工作内容需要涉及到相机开发,其中一个功能点就是实现一个相机预览页底部的滑动指示器,现在整理出来供大家讨论参考。
先上一张图看下效果:
主要实现功能有:
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() } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读
-
Android中判断listview是否滑动到顶部和底部的实现方法
-
Android用Scroller实现一个可向上滑动的底部导航栏
-
Android中使用ScrollView实现滑动到底部显示加载更多
-
Android实现美团APP的底部滑动菜单
-
Android progressbar实现带底部指示器和文字的进度条
-
Android实现小米相机底部滑动指示器
-
Android Jetpack 一个demo两分钟实现底部滑动导航栏
-
Android中使用ScrollView实现滑动到底部显示加载更多
-
android使用TabLayout实现底部图文结合滑动
-
Android用Scroller实现一个可向上滑动的底部导航栏