Android应用--新浪微博客户端新特性滚动视图和启动界面实现
新浪微博客户端新特性滚动视图和启动界面实现
2013年8月20日新浪微博客户端开发之启动界面实现
前言:
使用过新浪微博客户端的童鞋都清楚,客户端每一次升级之后第一次启动界面就会有新特性的介绍,用户通过左右滑动视图可以查看新的特性,查看完最后一个特性之后就进入了主界面了。如果再一次启动程序的时候,就不会再显示新特性介绍的视图了,就会有一个启动界面,延迟一小会然后直接进入主界面。现在很多的应用也是这样,一开始都会介绍这款新应用的一些特性的,这样感觉用户体验也比较良好。我想网上也有很多大神发表过相应的文章介绍这种功能的实现过程,不过我比较喜欢穿一手鞋,记录下自己开发的点滴,这也是分享技术的好去处。
我就用官方新浪微博客户端的新特性来展示这项功能的实现:
上面就是界面效果,下面来看代码实现。
只贴功能滚动视图的布局文件,其他的可以到我的资源页下载源码参考
下载地址:http://download.csdn.net/detail/wwj_748/5981415
/2013.08.20_Function_Scroller_Demo/res/layout/function_scroller.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <com.wwj.scroller.MyScrollLayout android:id="@+id/ScrollLayout" android:layout_width="match_parent" android:layout_height="match_parent" > <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/guide_1" > </FrameLayout> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/guide_2" > </FrameLayout><FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/guide_3" > </FrameLayout> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/guide_4" > </FrameLayout> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="#00000000" > </FrameLayout> </com.wwj.scroller.MyScrollLayout> <LinearLayout android:id="@+id/llayout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="16dp" android:orientation="horizontal" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dp" android:clickable="true" android:src="@drawable/guide_round" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dp" android:clickable="true" android:src="@drawable/guide_round" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dp" android:clickable="true" android:src="@drawable/guide_round" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dp" android:clickable="true" android:src="@drawable/guide_round" /> </LinearLayout> </RelativeLayout>
注意上面的滚动视图是自定义的,所以要注意标签的编写格式,包名+文件名,写全了。
正式介绍自定义滚动视图的代码实现:
/2013.08.20_Function_Scroller_Demo/src/com/wwj/scroller/MyScrollLayout.java
package com.wwj.scroller; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewGroup; import android.widget.Scroller; /** * 自定义滑动视图 * @author Administrator * */ public class MyScrollLayout extends ViewGroup { private VelocityTracker mVelocityTracker; // 用于判断甩动手势 private static final int SNAP_VELOCITY = 600; // 滑动距离 private Scroller mScroller; // 滑动控制器 private int mCurScreen; // 当前屏幕 private int mDefaultScreen = 0; // 默认屏幕 private float mLastMotionX; private OnViewChangeListener mOnViewChangeListener; public MyScrollLayout(Context context) { super(context); init(context); } public MyScrollLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } public MyScrollLayout(Context context, AttributeSet attrs) { super(context, attrs); init(context); } // 初始化变量 private void init(Context context) { mCurScreen = mDefaultScreen; mScroller = new Scroller(context); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (changed) { int childLeft = 0; final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View childView = getChildAt(i); // 得到孩子 if (childView.getVisibility() != View.GONE) { final int childWidth = childView.getMeasuredWidth(); // 获取view测量的宽度 childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight()); childLeft += childWidth; } } } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); final int width = MeasureSpec.getSize(widthMeasureSpec); final int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); } scrollTo(mCurScreen * width, 0); // 设置滚动视图的位置 } // 滑动到目标位置 public void snapToDestination() { final int screenWidth = getWidth(); final int destScreen = (getScrollX() + screenWidth / 2) / screenWidth; snapToScreen(destScreen); } // 滑动到屏幕 public void snapToScreen(int whichScreen) { // 获得有效的页面 whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1)); if (getScrollX() != (whichScreen * getWidth())) { final int delta = whichScreen * getWidth() - getScrollX(); mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2); mCurScreen = whichScreen; invalidate(); // 重绘布局 if (mOnViewChangeListener != null) { mOnViewChangeListener.OnViewChange(mCurScreen); } } } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } } @Override public boolean onTouchEvent(MotionEvent event) { final int action = event.getAction(); final float x = event.getX(); switch (action) { case MotionEvent.ACTION_DOWN: // 手指按下动作 if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); // 得到一个新的甩动手势 mVelocityTracker.addMovement(event); } if (!mScroller.isFinished()) { mScroller.abortAnimation(); } mLastMotionX = x; break; case MotionEvent.ACTION_MOVE: int deltaX = (int) (mLastMotionX - x); if(IsCanMove(deltaX)) { if(mVelocityTracker != null) { mVelocityTracker.addMovement(event); } mLastMotionX = x; scrollBy(deltaX, 0); } break; case MotionEvent.ACTION_UP: // 手指抬起 int velocityX = 0; if (mVelocityTracker != null) { mVelocityTracker.addMovement(event); mVelocityTracker.computeCurrentVelocity(1000); velocityX = (int) mVelocityTracker.getXVelocity(); } if (velocityX > SNAP_VELOCITY && mCurScreen > 0) { // 往左移动 snapToScreen(mCurScreen - 1); } else if (velocityX < - SNAP_VELOCITY && mCurScreen < getChildCount() - 1) { // 往右移动 snapToScreen(mCurScreen + 1); } else { snapToDestination(); } if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } break; } return true; } /** * 判断是否可以移动 * @param deltaX * @return */ private boolean IsCanMove(int deltaX) { if (getScrollX() <= 0 && deltaX < 0) { return false; } if (getScrollX() >= (getChildCount() - 1) * getWidth() && deltaX > 0) { return false; } return true; } public void SetOnViewChangeListener(OnViewChangeListener listener) { mOnViewChangeListener = listener; } // 接口 public interface OnViewChangeListener { public void OnViewChange(int View); } }
接着是启动界面的实现了
/2013.08.20_Function_Scroller_Demo/src/com/wwj/scroller/SplashActivity.java
package com.wwj.scroller; import android.os.Bundle; import android.os.Handler; import android.app.Activity; import android.content.Intent; /** * 程序功能:实现滚动显示新功能介绍 第一次启动程序的时候用户左右滑动查看新特性,查看完之后进入主界面 再次启动的时候直接进入主界面 * * @author wwj * */ public class SplashActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.splash); // 判断功能介绍界面是否显示过 boolean isPlayed = SettingUtil.get(this, SettingUtil.FUNCTION_SCROLLER_PLAYED, false); if (!isPlayed) { // 进入功能介绍界面 startActivity(new Intent(this, FunctionScroller.class)); finish(); return; } // 延迟进入 new Handler().postDelayed(new Runnable() { @Override public void run() { startActivity(new Intent(SplashActivity.this, WeiboMain.class)); finish(); } }, 2500); } }
新特性介绍的Activity,每次滚动一个视图都要表示视图所在位置,就是下面的点指示器的改变实现。
/2013.08.20_Function_Scroller_Demo/src/com/wwj/scroller/FunctionScroller.java
package com.wwj.scroller; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageView; import android.widget.LinearLayout; import com.wwj.scroller.MyScrollLayout.OnViewChangeListener; public class FunctionScroller extends Activity implements OnClickListener{ private MyScrollLayout mScrollLayout; // 滑动视图 private ImageView[] mImageViews; // 点图片 private int mViewCount; // 视图个数 private int currentPosition = 0; // 当前位置 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.function_scroller); findViews(); init(); } private void findViews() { mScrollLayout = (MyScrollLayout) findViewById(R.id.ScrollLayout); LinearLayout linearLayout = (LinearLayout) findViewById(R.id.llayout); mViewCount = mScrollLayout.getChildCount(); mImageViews = new ImageView[mViewCount - 1]; // 最后一个view是黑屏过度,所以- 1 for (int i = 0; i < (mViewCount - 1); i++) { mImageViews[i] = (ImageView) linearLayout.getChildAt(i); mImageViews[i].setEnabled(true); mImageViews[i].setTag(i); mImageViews[i].setOnClickListener(this); } } private void init() { mImageViews[currentPosition].setEnabled(false); mScrollLayout.SetOnViewChangeListener(new OnViewChangeListener() { @Override public void OnViewChange(int index) { if (index == mViewCount - 1) { // 记录滚屏已经播放过,以后不再播放 SettingUtil.set(FunctionScroller.this, SettingUtil.FUNCTION_SCROLLER_PLAYED, true); startActivity(new Intent(FunctionScroller.this, WeiboMain.class)); finish(); } setCurPoint(index); } }); } /** * 设置位置显示 * @param index */ private void setCurPoint(int index) { if (index < 0 || index > mViewCount - 2 || currentPosition == index) { return; } mImageViews[currentPosition].setEnabled(true); mImageViews[index].setEnabled(false); currentPosition = index; } @Override public void onClick(View v) { int pos = (Integer) (v.getTag()); setCurPoint(pos); mScrollLayout.snapToScreen(pos); } }
核心代码就是以上的了,实现起来也并不太复杂。童鞋们快快整合到你们的应用上面去吧。
关于新浪微博客户端的开发进度比较慢,因为平时要工作,也并不是时刻都有精力去写博客和编写代码的,程序员也需要生活,代码并不是一切,各位程序员们要注意身体啊。下一篇博客就会介绍主界面的实现了,可能并不能实现官方那样的效果,一些复杂的界面效果由于本人的能力有限也没办法实现,不过作为学习和实战已经够用了。