Android實(shí)現(xiàn)小米相機(jī)底部滑動指示器
近期工作內(nèi)容需要涉及到相機(jī)開發(fā),其中一個功能點(diǎn)就是實(shí)現(xiàn)一個相機(jī)預(yù)覽頁底部的滑動指示器,現(xiàn)在整理出來供大家討論參考。
先上一張圖看下效果:

主要實(shí)現(xiàn)功能有:
1.支持左右滑動,每次滑動一個tab
2.支持tab點(diǎn)擊,直接跳到對應(yīng)tab
3.選中的tab一直處于居中位置
4.支持部分UI自定義(大家可根據(jù)需要自己改動)
5.tab點(diǎn)擊回調(diào)
6.內(nèi)置Tab接口,放入的內(nèi)容需要實(shí)現(xiàn)Tab接口
7.設(shè)置預(yù)選中tab
public class CameraIndicator extends LinearLayout {
// 當(dāng)前選中的位置索引
private int currentIndex;
//tabs集合
private Tab[] tabs;
// 利用Scroller類實(shí)現(xiàn)最終的滑動效果
public Scroller mScroller;
//滑動執(zhí)行時間(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之初就已確認(rèn)該ViewGroup寬高都是match_parent,那么直接設(shè)置即可
setMeasuredDimension(widthSize, heightSize);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//給選中text的添加背景會多次進(jìn)入onLayout,會導(dǎo)致位置有問題,暫未解決
if (onLayoutCount > 0) {
return;
}
onLayoutCount++;
int counts = getChildCount();
int childLeft = 0;
int childRight = 0;
int childTop = 0;
int childBottom = 0;
//居中顯示
int widthOffset = 0;
//計(jì)算最左邊的子view距離中心的距離
for (int i = 0; i < currentIndex; i++) {
View childView = getChildAt(i);
widthOffset += childView.getMeasuredWidth() + getMargins(childView).get(0)+getMargins(childView).get(2);
}
//計(jì)算出每個子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()) {
// 滑動未結(jié)束,內(nèi)部使用scrollTo方法完成實(shí)際滑動
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));
}
/**
* 滑到目標(biāo)view
*
* @param view 目標(biāo)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會根據(jù)起點(diǎn)、終點(diǎn)及時間計(jì)算出每次滑動的距離,其內(nèi)部有一個插值器
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();
}
}
}
}
/**
* 設(shè)置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;
}
//設(shè)置默認(rèn)選中第幾個
public void setCurrentIndex(int currentIndex) {
this.currentIndex = currentIndex;
}
//設(shè)置滑動時間
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", "人像", "夜景", "萌拍", "全景", "專業(yè)")
lateinit var imageAnalysis:ImageAnalysis
override fun initView() {
//實(shí)現(xiàn)了CameraIndicator.Tab的對象
val map = tabs.map {
CameraIndicator.Tab { it }
}?.toTypedArray() ?: arrayOf()
//將tab集合設(shè)置給cameraIndicator,(binding.cameraIndicator即xml布局里的控件)
binding.cameraIndicator.setTabs(*map)
//默認(rèn)選中 拍照
binding.cameraIndicator.currentIndex = 3
//點(diǎn)擊某個tab的回調(diào)
binding.cameraIndicator.setSelectedTextBackgroundResources(R.drawable.selected_text_bg)
binding.cameraIndicator.setOnSelectedChangedListener { index, tag ->
Toast.makeText(this,tag.text,Toast.LENGTH_SHORT).show()
}
}
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android webveiw 出現(xiàn)棧錯誤解決辦法
這篇文章主要介紹了Android webveiw 出現(xiàn)棧錯誤解決辦法的相關(guān)資料,出現(xiàn)java.lang.UnsupportedOperationException: For security reasons, WebView is not allowed in privileged processes,這里提供解決辦法,需要的朋友可以參考下2017-08-08
Android反編譯看看手Q口令紅包的實(shí)現(xiàn)原理
這篇文章主要介紹了Android反編譯看看手Q口令紅包的實(shí)現(xiàn)原理,需要的朋友可以參考下2016-02-02
Android使用Sqlite存儲數(shù)據(jù)用法示例
這篇文章主要介紹了Android使用Sqlite存儲數(shù)據(jù)的方法,結(jié)合實(shí)例形式分析了Android操作SQLite數(shù)據(jù)庫的相關(guān)步驟與操作技巧,需要的朋友可以參考下2016-11-11
Android中控制和禁止ScrollView自動滑動到底部的方法
這篇文章主要給大家介紹了關(guān)于Android中控制和禁止ScrollView自動滑動到底部的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對各位Android開發(fā)者們具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10
詳解Android使用Gradle統(tǒng)一配置依賴管理
本篇文章主要介紹了詳解Android 使用 Gradle 統(tǒng)一配置依賴管理,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-01-01

