自定义ViewGroup实现水平滑动
2014-03-17 21:35
281 查看
最近由于工作上的需求,需要实现水平滑动的功能,在网上找了一些例子没有现成的,很多人都说可以使用ViewGroup来实现
效果图
点击右边的按钮可以实现动画切换页
目录结构
关键代码实现HScrollViewGroup.java
自定义控件在布局文件中的使用
在Activity中进行切换
http://download.csdn.net/detail/deng0zhaotai/7055623
效果图
点击右边的按钮可以实现动画切换页
目录结构
关键代码实现HScrollViewGroup.java
package com.example.listviewitem; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.Scroller; /** * 水平滑动或翻页,目前只支持水平滑动 2014.03.17 * * @author Administrator * */ public class HScrollViewGroup extends ViewGroup { private static final String TAG = "HScrollViewGroup_dzt"; private static final int TOUCH_STATE_REST = 0; private static final int TOUCH_STATE_SCROLLING = 1; private static final int SNAP_VELOCITY = 400;// 滑动视图的速率 private static final int INTERVAL = 4; // 每次滑动的间隔 private Scroller mScroller; // 滑动控件 private VelocityTracker mVelocityTracker; // 速度追踪器 private Direction direction = Direction.NONE; private int mCurScreen; // 记录当前页 private int mDefaultScreen = 0; // 默认页 private int mTouchState = TOUCH_STATE_REST;// 设置触发状态 private int mTouchSlop; // 触发移动的像素距离 private float mLastMotionX; // 手指触碰屏幕的最后一次x坐标 private float mLastMotionY; // 手指触碰屏幕的最后一次y坐标 private int mTotalPage; // 总页数 private int mMaxWidth; // 所有子控件加起来的总宽度 private int mWidth; // 每个子控件的宽度 private int mCtrlWidth = 0; private int mRemainder; // 总宽度除以每页的余数 private int mMoveCount; // 移动计数器 int[] mScreens = new int[5];// 每页的最前一个坐标 public HScrollViewGroup(Context context) { super(context); // TODO Auto-generated constructor stub init(context); } public HScrollViewGroup(Context context, AttributeSet attrs) { this(context, attrs, 0); // TODO Auto-generated constructor stub } public HScrollViewGroup(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub init(context); } private void init(Context context) { mScroller = new Scroller(context); mCurScreen = mDefaultScreen;// 默认设置显示第一个VIEW mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } /** * 父类为子类在屏幕上分配实际的宽度和高度,里面的四个参数分别表示,布局是否发生改变,布局左 上右下的边距 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // TODO Auto-generated method stub Log.d(TAG, "onLayout changed = " + changed); if (changed) { int childLeft = 0; final int childCount = getChildCount(); Log.d(TAG, "childCount = " + childCount); for (int i = 0; i < childCount; i++) { final View childView = getChildAt(i); if (childView.getVisibility() != View.GONE) { if (childCount > 5 && ((i != 0) && ((i % 4 == 0)) || (i == childCount - 1))) { childView.layout(childLeft, 0, childLeft + (mWidth + mRemainder), childView.getMeasuredHeight()); childLeft += (mWidth + mRemainder); } else { childView.layout(childLeft, 0, childLeft + mWidth, childView.getMeasuredHeight()); childLeft += mWidth; } Log.d(TAG, "childLeft=" + childLeft + " childWidth=" + mWidth); } } calculateScreens(); } } /** * 计算每页第一个item的位置 */ void calculateScreens() { int childLeft = 0; int viewWidth = getWidth(); int curPage = 0; mScreens[curPage] = childLeft; ++curPage; final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View childView = getChildAt(i); if (childView.getVisibility() != View.GONE) { if (childCount > 5 && ((i != 0) && ((i % 4 == 0)) || (i == childCount - 1))) { childLeft += (mWidth + mRemainder); if (childLeft > (viewWidth)) { mScreens[curPage] = childLeft - (mWidth + mRemainder) + mScreens[curPage - 1]; ++curPage; childLeft = (mWidth + mRemainder); } } else { childLeft += mWidth; if (childLeft > (viewWidth)) { mScreens[curPage] = childLeft - mWidth + mScreens[curPage - 1]; ++curPage; childLeft = mWidth; } } } Log.d(TAG, "childLeft = " + childLeft); } if (childLeft != 0 && curPage > 1) { mScreens[curPage - 1] = mScreens[curPage - 1] + childLeft - viewWidth; } } /** * MeasureSpec类的静态方法getMode和getSize来译解。一个MeasureSpec包含一个尺寸和模式。 * * 有三种可能的模式: * * UNSPECIFIED:父布局没有给子布局任何限制,子布局可以任意大小。 * EXACTLY:父布局决定子布局的确切大小。不论子布局多大,它都必须限制在这个界限里 * 。(当布局定义为一个固定像素或者fill_parent时就是EXACTLY模式) * AT_MOST:子布局可以根据自己的大小选择任意大小。(当布局定义为wrap_content时就是AT_MOST模式) */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec); final int width = MeasureSpec.getSize(widthMeasureSpec); final int height = MeasureSpec.getSize(heightMeasureSpec); if (mCtrlWidth != width) { mCtrlWidth = width; mWidth = width / 5; mRemainder = width % 5; // The children are given the same width and height as the // scrollLayout final int count = getChildCount(); for (int i = 0; i < count; i++) { if (count > 5 && ((i != 0) && ((i % 4 == 0)) || (i == count - 1))) { getChildAt(i).measure((mWidth + mRemainder), heightMeasureSpec); } else { getChildAt(i).measure(mWidth, heightMeasureSpec); } } mMaxWidth = (getChildCount() * mWidth) + mRemainder; mTotalPage = mMaxWidth / width; snapToScreen(mCurScreen); mScroller.abortAnimation(); Log.d(TAG, "mTotalPage = " + mTotalPage + " width = " + width + " height = " + height + " count = " + count + " mCurScreen = " + mCurScreen); } } /** * 根据滑动的距离判断移动到第几个视图 */ public void snapToDestination() { final int screenWidth = getWidth(); final int scrollX = getScrollX() > mMaxWidth ? mMaxWidth : getScrollX(); final int destScreen = (scrollX + screenWidth / 2) / screenWidth; Log.d(TAG, "screenWidth = " + screenWidth + " destScreen = " + destScreen + " scrollx = " + scrollX); snapToScreen(destScreen); } /** * 滚动到制定的视图 * * @param whichScreen * 视图下标 */ public void snapToScreen(int whichScreen) { whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1)); if (getScrollX() != (whichScreen * getWidth())) { // final int delta = whichScreen * getWidth() - getScrollX(); final int delta = mScreens[mCurScreen] - getScrollX(); Log.d(TAG, "snapToScreen-whichScreen = " + whichScreen + " delta = " + delta + " scrollX = " + getScrollX()); mScroller.startScroll(getScrollX(), 0, delta, 0, 2000); mCurScreen = whichScreen; mMoveCount = getScrollX(); invalidate(); } } public void setDirection(Direction dir) { direction = dir; } public int getCurScreen() { return mCurScreen; } @Override public void computeScroll() { // TODO Auto-generated method stub if (mScroller.computeScrollOffset()) { if (direction == Direction.LEFT) { Log.d(TAG, "left mScreens[mCurScreen] = " + mScreens[mCurScreen]); mMoveCount -= INTERVAL; if (mMoveCount < 0) { mMoveCount = 0; mScroller.abortAnimation(); } scrollTo(mMoveCount, mScroller.getCurrY()); } else if (direction == Direction.RIGHT) { if (mScroller.getCurrX() <= mScreens[mCurScreen]) { Log.d(TAG, "right mScreens[mCurScreen] = " + mScreens[mCurScreen]); mMoveCount += INTERVAL; if (mMoveCount > mScreens[mCurScreen]) { mMoveCount = mScreens[mCurScreen]; mScroller.abortAnimation(); } scrollTo(mMoveCount, mScroller.getCurrY()); } else { scrollTo(mScreens[mCurScreen], mScroller.getCurrY()); mScroller.abortAnimation(); } } else { mScroller.forceFinished(true); } postInvalidate(); Log.d(TAG, "computeScroll----mMoveCount = " + mMoveCount); Log.d(TAG, "computeScroll----x = " + mScroller.getCurrX()); } } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); final int action = event.getAction(); final float x = event.getX(); switch (action) { case MotionEvent.ACTION_DOWN: if (!mScroller.isFinished()) { // mScroller.abortAnimation(); Log.d(TAG, "-----------onTouchEvent---ACTION_DOWN no finish"); return false; } mLastMotionX = x; Log.d(TAG, "down mLastMotionX = " + mLastMotionX); break; case MotionEvent.ACTION_MOVE: int deltaX = (int) (mLastMotionX - x); mLastMotionX = x; Log.d(TAG, "move scroll " + getScrollX() + " mCurScreen = " + mCurScreen + " mTotalPage = " + mTotalPage + " deltaX = " + deltaX); if (getScrollX() > 0 && mCurScreen < mTotalPage) scrollBy(deltaX, 0); break; case MotionEvent.ACTION_UP: final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000); int velocityX = (int) velocityTracker.getXVelocity(); if (velocityX > SNAP_VELOCITY && mCurScreen > 0) { // 向左移动 Log.d(TAG, "left mCurScreen = " + mCurScreen); direction = Direction.LEFT; snapToScreen(mCurScreen - 1); } else if (velocityX < -SNAP_VELOCITY && mCurScreen < mTotalPage) { // 向右移动 Log.d(TAG, "right mCurScreen = " + mCurScreen); direction = Direction.RIGHT; snapToScreen(mCurScreen + 1); } else { direction = Direction.NONE; snapToDestination(); } if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } mTouchState = TOUCH_STATE_REST; break; case MotionEvent.ACTION_CANCEL: mTouchState = TOUCH_STATE_REST; break; } return true; } /** * 用于拦截手势事件的,每个手势事件都会先调用这个方法。Layout里的onInterceptTouchEvent默认返回值是false, * 这样touch事件会传递到View控件 */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // TODO Auto-generated method stub final int action = ev.getAction(); if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) { return true; } final float x = ev.getX(); final float y = ev.getY(); switch (action) { case MotionEvent.ACTION_MOVE: final int xDiff = (int) Math.abs(mLastMotionX - x); if (xDiff > mTouchSlop) { mTouchState = TOUCH_STATE_SCROLLING; } break; case MotionEvent.ACTION_DOWN: mLastMotionX = x; mLastMotionY = y; mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING; break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: mTouchState = TOUCH_STATE_REST; break; } return mTouchState != TOUCH_STATE_REST; } /** * 滑动的方向 * * @author Administrator * */ public enum Direction { LEFT, RIGHT, NONE } }主要思路:ViewGroup在创建时会调用onMeasure回调函数计算控件的大小,调用getChildAt(i).measure()设置每个子控件的大小,设置完后会回调void onLayout(boolean changed, int l, int t, int r, int b)设置控件的布局,在ViewGroup中的子控件都会加载进来,这样就不能存放过多的控件,否则会影响性能;设置好每个控件的位置后就需要保存每页第一个子控件的位置,这样在滑动时才不会超过第一个控件和最后一个的位置。还有就是滑动时会回调computeScroll()函数来计算每将滑动的偏移位置,但有时滑动的速度不是我们需要的就需要自己去实现滑动偏移大小。
自定义控件在布局文件中的使用
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rela_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:id="@+id/move" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:background="@drawable/next_page_btn_selector" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBottom="@id/move" android:layout_alignTop="@id/move" android:layout_toLeftOf="@id/move" > <com.example.listviewitem.HScrollViewGroup xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/hsView" android:layout_width="wrap_content" android:layout_height="wrap_content" > <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/scan_btn_selector" android:contentDescription="@string/content_description" /> <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/micro_sub_btn_selector" android:contentDescription="@string/content_description" /> <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/micro_add_btn_selector" android:contentDescription="@string/content_description" /> <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/previous_btn_selector" android:contentDescription="@string/content_description" /> <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/next_btn_selector" android:contentDescription="@string/content_description" /> <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/micro_add_btn_selector" android:contentDescription="@string/content_description" /> <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/previous_btn_selector" android:contentDescription="@string/content_description" /> <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/scan_btn_selector" android:contentDescription="@string/content_description" /> </com.example.listviewitem.HScrollViewGroup> </LinearLayout> </RelativeLayout>
在Activity中进行切换
package com.example.listviewitem; import android.app.Activity; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.RelativeLayout; import com.example.listviewitem.HScrollViewGroup.Direction; public class buttonMoveActivity extends Activity { private HScrollViewGroup hsView; private RelativeLayout layout; private boolean show = true; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.button_move); layout = (RelativeLayout) findViewById(R.id.rela_layout); hsView = (HScrollViewGroup) findViewById(R.id.hsView); final Button btn = (Button) findViewById(R.id.move); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub if (hsView.getCurScreen() > 0) { btn.setBackgroundResource(R.drawable.next_page_btn_selector); hsView.setDirection(Direction.LEFT); hsView.snapToScreen(0); } else { btn.setBackgroundResource(R.drawable.previous_page_btn_selector); hsView.setDirection(Direction.RIGHT); hsView.snapToScreen(1); } } }); } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub if (event.getAction() == MotionEvent.ACTION_DOWN) { if (show) { show = false; layout.setVisibility(View.GONE); } else { show = true; layout.setVisibility(View.VISIBLE); } } return super.onTouchEvent(event); } }如果需要完整代码可以从以下链接下载
http://download.csdn.net/detail/deng0zhaotai/7055623
相关文章推荐
- 自定义ViewGroup实现水平布局空间不足自动换行的效果
- Android自定义组件系列【4】——自定义ViewGroup实现双侧滑动
- Android自定义组件系列【4】——自定义ViewGroup实现双侧滑动
- 自定义ViewGroup (支持margin,gravity以及水平,垂直排列,滑动和点击事件)
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu[转]
- 自定义ViewGroup实现纵向滑动切换的ViewPager
- Android自定义ViewGroup实现弹性滑动效果
- 自定义View继承ViewGroup自定义属性,实现水平垂直阶梯的排列
- Android自定义组件系列【4】——自定义ViewGroup实现双侧滑动
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu
- HorizontalScrollView下自定义ViewGroup无法实现滑动
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu
- 如何实现类似水平WheelView的自定义滑动控件
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu
- Android自定义ViewGroup自动换行实现滑动任意布局及事件处理效果
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu
- (转)android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu