都说自定义View是小白和中级开发者的分界线,这也看出来自定义View在Android开发过程中的重要性,所以我的博客初期会以各种各样的自定义View为主。不会讲述太多的原理,主要提供一种简单易懂的实现。
本期要实现的是可循环拉动的动画菜单。这个控件我在其他博客上看到过不少的实现方法,但是大多数要么就是代码量太大,要么就是结构太复杂,对新手来说阅读和理解起来比较困难,所以我今天花了一些时间来实现一个简单可用的循环滚动菜单控件。
先看看效果图,这种控件提供了横向和纵向的实现
接下来进行代码讲解
代码主要是继承LinearLayout来实现的ScrollerLayout,共有21个方法,当其中只有5个是重要的,其他都是些数值设置初始化相关的方法。下面先来看看自定义的变量
/**
* 公用的变量
*/
private Context myContext;//上下文变量
private Scroller mScroller;//用于操控滚动的Scroller变量
private int mTouchSlop;//用于判断触碰操作是点击还是滚动
private int itemPadding;//子元素间距
private int moveDirection;//点击移动的方向
private int mOrientation;//布局方向
private int visiableItemNum;//屏幕可见的子元素个数
private ArrayList<View> childViewList;//子元素列表
private int next;//下一个要显示的子元素索引
private int front;//上一个要显示的子元素索引
private int totalNum;//全部子元素个数
/**
* LinearLayout为横向时使用的变量
*/
private float mXDownPos;//点击位置的x坐标
private float mXMovePos;//点击并移动后的x坐标
private float mXLastMovePos;//上一次触发滑动事件的位置
private int leftBorder;//左界限
private int rightBorder;//右界限
private int width;//屏幕宽度
private int displayWidth;//显示的子元素的宽度
/**
* LinearLayout为纵向时使用的变量
*/
private int height;//屏幕高度
private float mYDownPos;//点击位置的y坐标
private float mYMovePos;//点击并移动后的y坐标
private float mYLastMovePos;//上一次触发滑动事件的位置
private int topBorder;//上界限
private int bottomBorder;//下界限
private int displayHeight;//显示的子元素的高度
private int statusBarHeight;//状态栏高度
private int titleBarHeight;//标题栏高度
private int navigationBarHeight;//底部导航栏(部分Android手机的按键高度也属于导航栏高度)
之前说过这个控件提供了横向和纵向的功能,只需要在XML布局文件中将android:orientation进行设置即可。下面的是所有与数值处理有关的方法,共16个
/**
* 构造函数
*/
public ScrollerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
this.setGravity(Gravity.CENTER_VERTICAL);
this.myContext=context;
init();
}
/**
* 初始化
*/
public void init(){
this.mScroller = new Scroller(this.myContext);//创建Scroller实例
ViewConfiguration configuration = ViewConfiguration.get(this.myContext);//获取TouchSlop值
this.mTouchSlop = configuration.getScaledTouchSlop();
}
/**
* 设置屏幕上可见的子元素个数
* @param value
*/
public void initVisiableItemNum(int value){
this.visiableItemNum=(value==0)?getChildCount():value;
initTotalNum();//获取子元素总个数
initFront();//获取上一个要显示的子元素索引
initNext();//获取下一个要显示的子元素索引
}
/**
* 获得子元素列表
*/
private void initChildViewList(){
this.childViewList=new ArrayList<View>();
int childCount = getChildCount();//获取包含的子元素的个数
for (int i = 0; i < childCount; i++) {
this.childViewList.add(getChildAt(i));
}
}
/**
* 初始化状态栏高度
*/
private void initStatusBarHeight(){
int result = 0;
int resourceId = this.myContext.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = this.myContext.getResources().getDimensionPixelSize(resourceId);
}
this.statusBarHeight=result;
}
/**
* 初始化导航栏高度
*/
private void initNavigationBarHeight(){
int result=0;
Resources resources = this.myContext.getResources();
int resourceId=resources.getIdentifier("navigation_bar_height","dimen","android");
if(resourceId!=0)
this.navigationBarHeight = resources.getDimensionPixelSize(resourceId);
}
/**
* 初始化标题栏高度
*/
private void initTitleBarHeight(){
TypedValue tv = new TypedValue();
if (this.myContext.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
this.titleBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, this.myContext.getResource