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

主要实现功能有:
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()
}
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
角落loser