【0089】【项目实战】-【谷歌电子市场】-【01】【项目介绍】【自定义Application】【UIUtils封装】【导入PagerTab并解决相关报错】【主页面框架搭建】【LoadingPage-加载中布局】【自定义进度条】【LoadingPage-加载失败布局】【数据为空的布局的加载】【LoadingPage-根据状态显示页面】【onCreateSuccessView实现】
1.项目介绍
【服务器】在手机上安装了一个服务器;


【首页】

【下载的页面】具有断点续传的功能;




【推荐】随机显示的数据;

【分类的页面】

【排行的页面】颜色宽高都是随机的;

【侧边栏】

2.如何运行项目
2.1 目录介绍

2.2 项目的编码
【说明】使用utf-8编码;


2.3 WebServer启动和资源
【说明】需要在手机客户端启动WebServer的应用程序;
资源文件是放在sd卡中的;
需要将资源文件放到sd卡的根目录下;

【image目录】项目中使用的图片等

【app目录】


3. 自定义Application
【说明】定义一些常用的全局的变量;



4.UIUtils封装
【说明】整个项目全局的获取一些常用的信息,Application类定义的常用的全局的类 都可以从此类中获取到;

【获取字符串】

、
【获取字符串数组】


【获取图片-颜色】


【获取尺寸】


【dp和px之间的转换】

【加载布局文件】

【判断是否运行在主线程】

【运行在中主线程】

【完整源码】/GooglePlay74/src/com/itheima/googleplay74/utils/UIUtils.java
1 package com.itheima.googleplay74.utils; 2 3 import android.content.Context; 4 import android.content.res.ColorStateList; 5 import android.graphics.drawable.Drawable; 6 import android.os.Handler; 7 import android.view.View; 8 9 import com.itheima.googleplay74.global.GooglePlayApplication; 10 11 public class UIUtils { 12 13 public static Context getContext() { 14 return GooglePlayApplication.getContext(); 15 } 16 17 public static Handler getHandler() { 18 return GooglePlayApplication.getHandler(); 19 } 20 21 public static int getMainThreadId() { 22 return GooglePlayApplication.getMainThreadId(); 23 } 24 25 // /////////////////加载资源文件 /////////////////////////// 26 27 // 获取字符串 28 public static String getString(int id) { 29 return getContext().getResources().getString(id); 30 } 31 32 // 获取字符串数组 33 public static String[] getStringArray(int id) { 34 return getContext().getResources().getStringArray(id); 35 } 36 37 // 获取图片 38 public static Drawable getDrawable(int id) { 39 return getContext().getResources().getDrawable(id); 40 } 41 42 // 获取颜色 43 public static int getColor(int id) { 44 return getContext().getResources().getColor(id); 45 } 46 47 //根据id获取颜色的状态选择器 48 public static ColorStateList getColorStateList(int id) { 49 return getContext().getResources().getColorStateList(id); 50 } 51 52 // 获取尺寸 53 public static int getDimen(int id) { 54 return getContext().getResources().getDimensionPixelSize(id);// 返回具体像素值 55 } 56 57 // /////////////////dip和px转换////////////////////////// 58 59 public static int dip2px(float dip) { 60 float density = getContext().getResources().getDisplayMetrics().density; 61 return (int) (dip * density + 0.5f); 62 } 63 64 public static float px2dip(int px) { 65 float density = getContext().getResources().getDisplayMetrics().density; 66 return px / density; 67 } 68 69 // /////////////////加载布局文件////////////////////////// 70 public static View inflate(int id) { 71 return View.inflate(getContext(), id, null); 72 } 73 74 // /////////////////判断是否运行在主线程////////////////////////// 75 public static boolean isRunOnUIThread() { 76 // 获取当前线程id, 如果当前线程id和主线程id相同, 那么当前就是主线程 77 int myTid = android.os.Process.myTid(); 78 if (myTid == getMainThreadId()) { 79 return true; 80 } 81 82 return false; 83 } 84 85 // 运行在主线程 86 public static void runOnUIThread(Runnable r) { 87 if (isRunOnUIThread()) { 88 // 已经是主线程, 直接运行 89 r.run(); 90 } else { 91 // 如果是子线程, 借助handler让其运行在主线程 92 getHandler().post(r); 93 } 94 } 95 96 }
5.导入PagerTab并解决相关报错
5.1【说明】在主页当中搭建指示器

5.2 小技巧
【说明】将一个项目中的文件移动的时候一定要使用refactor->move;
这样相关类的关系都可以移动修改。


5.3 将PagerTab.java源码导入及错误修改
【修改的错误】

【PagerTab.java源码】未修改的文件
1 package com.mwqi.ui.widget; 2 3 import android.content.Context; 4 import android.graphics.Canvas; 5 import android.graphics.Paint; 6 import android.graphics.Typeface; 7 import android.support.v4.view.ViewCompat; 8 import android.support.v4.view.ViewPager; 9 import android.support.v4.view.ViewPager.OnPageChangeListener; 10 import android.support.v4.widget.EdgeEffectCompat; 11 import android.support.v4.widget.ScrollerCompat; 12 import android.util.AttributeSet; 13 import android.util.TypedValue; 14 import android.view.*; 15 import android.widget.ImageButton; 16 import android.widget.TextView; 17 import com.mwqi.R; 18 import com.mwqi.ui.activity.BaseActivity; 19 import com.mwqi.utils.UIUtils; 20 21 public class PagerTab extends ViewGroup { 22 23 private ViewPager mViewPager; 24 private PageListener mPageListener = new PageListener();//用于注册给ViewPager监听状态和滚动 25 private OnPageChangeListener mDelegatePageListener;//用于通知外界ViewPager的状态和滚动 26 private BaseActivity mActivity; 27 28 private int mDividerPadding = 12;// 分割线上下的padding 29 private int mDividerWidth = 1;// 分割线的宽度 30 private int mDividerColor = 0x1A000000;//分割线颜色 31 private Paint mDividerPaint;//分割线的画笔 32 33 private int mIndicatorHeight = 4;//指示器的高度 34 private int mIndicatorWidth;//指示器的宽度,是动态的随着tab的宽度变化 35 private int mIndicatorLeft;//指示器的距离左边的距离 36 private int mIndicatorColor = 0xFF0084FF;//指示器颜色 37 private Paint mIndicatorPaint; //指示器的画笔 38 39 private int mContentWidth;//记录自身内容的宽度 40 private int mContentHeight;//记录自身内容的高度 41 42 private int mTabPadding = 24;// tab左右的内边距 43 private int mTabTextSize = 16; //tab文字大小 44 private int mTabBackgroundResId = R.drawable.bg_tab_text;// tab背景资源 45 private int mTabTextColorResId = R.color.tab_text_color; //tab文字颜色 46 private int mTabCount;//tab的个数 47 48 private int mCurrentPosition = 0;//当前光标所处的tab,规则是以光标的最左端所在的item的position 49 private float mCurrentOffsetPixels;//光标左边距离当前光标所处的tab的左边距离 50 private int mSelectedPosition = 0; //当前被选中的tab,用于记录手指点击tab的position 51 52 private boolean mIsBeingDragged = false;//是否处于拖动中 53 private float mLastMotionX;//上一次手指触摸的x坐标 54 private VelocityTracker mVelocityTracker;//用于记录速度的帮助类 55 private int mMinimumVelocity;//系统默认的最小满足fling的速度 56 private int mMaximumVelocity;//系统默认最大的fling速度 57 private int mTouchSlop;//系统默认满足滑动的最小位移 58 59 private ScrollerCompat mScroller;//处理滚动的帮助者 60 private int mLastScrollX;//记录上一次滚动的x位置,这是用于处理overScroll,实际位置可能会受到限制 61 62 private int mMaxScrollX = 0;// 控件最大可滚动的距离 63 private int mSplitScrollX = 0;// 根据item的个数,计算出每移动一个item控件需要移动的距离 64 65 private EdgeEffectCompat mLeftEdge;//处理overScroll的反馈效果 66 private EdgeEffectCompat mRightEdge; 67 68 public PagerTab(Context context) { 69 this(context, null); 70 } 71 72 public PagerTab(Context context, AttributeSet attrs) { 73 this(context, attrs, 0); 74 } 75 76 public PagerTab(Context context, AttributeSet attrs, int defStyle) { 77 super(context, attrs, defStyle); 78 if (context instanceof BaseActivity) { 79 mActivity = (BaseActivity) context; 80 } 81 init(); 82 initPaint(); 83 } 84 85 /** 初始化一些常量 */ 86 private void init() { 87 //把一个值从dip转换成px 88 mIndicatorHeight = UIUtils.dip2px(mIndicatorHeight); 89 mDividerPadding = UIUtils.dip2px(mDividerPadding); 90 mTabPadding = UIUtils.dip2px(mTabPadding); 91 mDividerWidth = UIUtils.dip2px(mDividerWidth); 92 mTabTextSize = UIUtils.dip2px(mTabTextSize); 93 //创建一个scroller 94 mScroller = ScrollerCompat.create(mActivity); 95 //获取一个系统关于View的常量配置类 96 final ViewConfiguration configuration = ViewConfiguration.get(mActivity); 97 //获取滑动的最小距离 98 mTouchSlop = configuration.getScaledTouchSlop(); 99 //获取fling的最小速度 100 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 101 //获取fling的最大速度 102 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 103 104 mLeftEdge = new EdgeEffectCompat(mActivity); 105 mRightEdge = new EdgeEffectCompat(mActivity); 106 } 107 108 /** 初始化笔 */ 109 private void initPaint() { 110 mIndicatorPaint = new Paint(); 111 mIndicatorPaint.setAntiAlias(true); 112 mIndicatorPaint.setStyle(Paint.Style.FILL); 113 mIndicatorPaint.setColor(mIndicatorColor); 114 115 mDividerPaint = new Paint(); 116 mDividerPaint.setAntiAlias(true); 117 mDividerPaint.setStrokeWidth(mDividerWidth); 118 mDividerPaint.setColor(mDividerColor); 119 } 120 121 /** 设置ViewPager */ 122 public void setViewPager(ViewPager viewPager) { 123 if (viewPager == null || viewPager.getAdapter() == null) { 124 throw new IllegalStateException("ViewPager is null or ViewPager does not have adapter instance."); 125 } 126 mViewPager = viewPager; 127 onViewPagerChanged(); 128 } 129 130 private void onViewPagerChanged() { 131 mViewPager.setOnPageChangeListener(mPageListener);//给ViewPager设置监听 132 mTabCount = mViewPager.getAdapter().getCount();//有多少个tab需要看ViewPager有多少个页面 133 for (int i = 0; i < mTabCount; i++) { 134 if (mViewPager.getAdapter() instanceof IconTabProvider) {//如果想要使用icon作为tab,则需要adapter实现IconTabProvider接口 135 addIconTab(i, ((IconTabProvider) mViewPager.getAdapter()).getPageIconResId(i)); 136 } else { 137 addTextTab(i, mViewPager.getAdapter().getPageTitle(i).toString()); 138 } 139 } 140 ViewTreeObserver viewTreeObserver = getViewTreeObserver(); 141 if (viewTreeObserver != null) {//监听第一个的全局layout事件,来设置当前的mCurrentPosition,显示对应的tab 142 viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 143 @Override 144 public void onGlobalLayout() { 145 getViewTreeObserver().removeGlobalOnLayoutListener(this);//只需要监听一次,之后通过listener回调即可 146 mCurrentPosition = mViewPager.getCurrentItem(); 147 if (mDelegatePageListener != null) { 148 mDelegatePageListener.onPageSelected(mCurrentPosition); 149 } 150 } 151 }); 152 } 153 } 154 155 /** 设置监听,因为Tab会监听ViewPager的状态,所以不要给ViewPager设置监听了,设置给Tab,由Tab转发 */ 156 public void setOnPageChangeListener(OnPageChangeListener listener) { 157 mDelegatePageListener = listener; 158 } 159 160 /** 添加文字tab */ 161 private void addTextTab(final int position, String title) { 162 TextView tab = new TextView(mActivity); 163 tab.setText(title); 164 tab.setGravity(Gravity.CENTER); 165 tab.setSingleLine(); 166 tab.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTabTextSize); 167 tab.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD)); 168 tab.setTextColor(UIUtils.getColorStateList(mTabTextColorResId)); 169 tab.setBackgroundDrawable(UIUtils.getDrawable(mTabBackgroundResId)); 170 tab.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)); 171 addTab(position, tab); 172 } 173 174 /** 添加图片icon */ 175 private void addIconTab(final int position, int resId) { 176 ImageButton tab = new ImageButton(mActivity); 177 tab.setImageResource(resId); 178 tab.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 179 addTab(position, tab); 180 } 181 182 private void addTab(final int position, View tab) { 183 tab.setFocusable(true); 184 //设置tab的点击事件,当tab被点击时候切换pager的页面 185 tab.setOnClickListener(new OnClickListener() { 186 @Override 187 public void onClick(View v) { 188 mViewPager.setCurrentItem(position); 189 } 190 }); 191 tab.setPadding(mTabPadding, 0, mTabPadding, 0); 192 addView(tab, position); 193 } 194 195 /** 测量时的回调 */ 196 @Override 197 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 198 // 获取控件自身的宽高,模式 199 int widthSize = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight(); 200 int heightSize = MeasureSpec.getSize(heightMeasureSpec) - getPaddingBottom() - getPaddingBottom(); 201 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 202 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 203 204 int totalWidth = 0; 205 int highest = 0; 206 int goneChildCount = 0; 207 for (int i = 0; i < mTabCount; i++) { 208 final View child = getChildAt(i); 209 if (child == null || child.getVisibility() == View.GONE) { 210 goneChildCount--; 211 continue; 212 } 213 int childWidthMeasureSpec; 214 int childHeightMeasureSpec; 215 216 LayoutParams childLayoutParams = child.getLayoutParams(); 217 if (childLayoutParams == null) { 218 childLayoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 219 } 220 221 if (childLayoutParams.width == LayoutParams.MATCH_PARENT) { 222 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY); 223 } else if (childLayoutParams.width == LayoutParams.WRAP_CONTENT) { 224 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST); 225 } else { 226 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childLayoutParams.width, MeasureSpec.EXACTLY); 227 } 228 229 if (childLayoutParams.height == LayoutParams.MATCH_PARENT) { 230 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY); 231 } else if (childLayoutParams.height == LayoutParams.WRAP_CONTENT) { 232 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.AT_MOST); 233 } else { 234 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childLayoutParams.height, MeasureSpec.EXACTLY); 235 } 236 237 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 238 239 int childWidth = child.getMeasuredWidth(); 240 int childHeight = child.getMeasuredHeight(); 241 242 totalWidth += childWidth; 243 highest = highest < childHeight ? childHeight : highest; 244 } 245 246 if (totalWidth <= widthSize) {//如果子Tab的总宽度小于PagerTab,则采用平分模式 247 int splitWidth = (int) (widthSize / (mTabCount - goneChildCount + 0.0f) + 0.5f); 248 for (int i = 0; i < mTabCount; i++) { 249 final View child = getChildAt(i); 250 if (child == null || child.getVisibility() == View.GONE) { 251 continue; 252 } 253 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(splitWidth, MeasureSpec.EXACTLY); 254 int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(), MeasureSpec.EXACTLY); 255 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 256 } 257 mMaxScrollX = 0; 258 mSplitScrollX = 0; 259 } else {//如果所有子View大于控件的宽度 260 mMaxScrollX = totalWidth - widthSize; 261 mSplitScrollX = (int) (mMaxScrollX / (mTabCount - goneChildCount - 1.0f) + 0.5f); 262 } 263 264 if (widthMode == MeasureSpec.EXACTLY) { 265 mContentWidth = widthSize; 266 } else { 267 mContentWidth = totalWidth; 268 } 269 270 if (heightMode == MeasureSpec.EXACTLY) { 271 mContentHeight = heightSize; 272 } else { 273 mContentHeight = highest; 274 } 275 276 int measureWidth = mContentWidth + getPaddingLeft() + getPaddingRight(); 277 int measureHeight = mContentHeight + getPaddingTop() + getPaddingBottom(); 278 setMeasuredDimension(measureWidth, measureHeight); 279 } 280 281 /** 布局时的回调 */ 282 @Override 283 protected void onLayout(boolean changed, int l, int t, int r, int b) {//这里简化了,没有考虑margin的情况 284 if (changed) { 285 int height = b - t;//控件供子View显示的高度 286 int left = l; 287 for (int i = 0; i < mTabCount; i++) { 288 final View child = getChildAt(i); 289 if (child == null || child.getVisibility() == View.GONE) { 290 continue; 291 } 292 int top = (int) ((height - child.getMeasuredHeight()) / 2.0f + 0.5f);//如果控件比tab要高,则居中显示 293 int right = left + child.getMeasuredWidth(); 294 child.layout(left, top, right, top + child.getMeasuredHeight());//摆放tab 295 left = right;//因为是水平摆放的,所以为下一个准备left值 296 } 297 } 298 } 299 300 /** 绘制时的回调 */ 301 @Override 302 protected void onDraw(Canvas canvas) { 303 super.onDraw(canvas); 304 final int height = getHeight(); 305 //画指示器 306 canvas.drawRect(mIndicatorLeft, height - mIndicatorHeight, mIndicatorLeft + mIndicatorWidth, height, mIndicatorPaint); 307 308 // 画分割线 309 for (int i = 0; i < mTabCount - 1; i++) {//分割线的个数比tab的个数少一个 310 final View child = getChildAt(i); 311 if (child == null || child.getVisibility() == View.GONE) { 312 continue; 313 } 314 if (child != null) { 315 canvas.drawLine(child.getRight(), mDividerPadding, child.getRight(), mContentHeight - mDividerPadding, mDividerPaint); 316 } 317 } 318 // 因为overScroll效果是一个持续效果,所以需要持续画 319 boolean needsInvalidate = false; 320 if (!mLeftEdge.isFinished()) {//如果效果没停止 321 final int restoreCount = canvas.save();//先保存当前画布 322 final int heightEdge = getHeight() - getPaddingTop() - getPaddingBottom(); 323 final int widthEdge = getWidth(); 324 canvas.rotate(270); 325 canvas.translate(-heightEdge + getPaddingTop(), 0); 326 mLeftEdge.setSize(heightEdge, widthEdge); 327 needsInvalidate |= mLeftEdge.draw(canvas); 328 canvas.restoreToCount(restoreCount); 329 } 330 if (!mRightEdge.isFinished()) { 331 final int restoreCount = canvas.save(); 332 final int widthEdge = getWidth(); 333 final int heightEdge = getHeight() - getPaddingTop() - getPaddingBottom(); 334 canvas.rotate(90); 335 canvas.translate(-getPaddingTop(), -(widthEdge + mMaxScrollX)); 336 mRightEdge.setSize(heightEdge, widthEdge); 337 needsInvalidate |= mRightEdge.draw(canvas); 338 canvas.restoreToCount(restoreCount); 339 } 340 if (needsInvalidate) { 341 postInvalidate(); 342 } 343 } 344 345 /** 触摸事件是否拦截的方法 */ 346 @Override 347 public boolean onInterceptTouchEvent(MotionEvent ev) { 348 final int action = ev.getAction(); 349 if (mIsBeingDragged && action == MotionEvent.ACTION_MOVE) {//当已经处于拖动,并且当前事件是MOVE,直接消费掉 350 return true; 351 } 352 switch (action) { 353 case MotionEvent.ACTION_DOWN: { 354 final float x = ev.getX(); 355 mLastMotionX = x; //记录住当前的x坐标 356 mIsBeingDragged = !mScroller.isFinished();//如果按下的时候还在滚动,则把状态处于拖动状态 357 break; 358 } 359 case MotionEvent.ACTION_MOVE: { 360 final float x = ev.getX(); 361 final int xDiff = (int) Math.abs(x - mLastMotionX);//计算两次的差值 362 if (xDiff > mTouchSlop) {//如果大于最小移动的距离,则把状态改变为拖动状态 363 mIsBeingDragged = true; 364 mLastMotionX = x; 365 ViewParent parent = getParent();//并请求父View不要再拦截自己触摸事件,交给自己处理 366 if (parent != null) { 367 parent.requestDisallowInterceptTouchEvent(true); 368 } 369 } 370 break; 371 } 372 case MotionEvent.ACTION_CANCEL://当手指离开或者触摸事件取消的时候,把拖动状态取消掉 373 case MotionEvent.ACTION_UP: 374 mIsBeingDragged = false; 375 break; 376 } 377 return mIsBeingDragged;//如果是拖动状态,则拦截事件,交给自己的onTouch处理 378 } 379 380 /** 触摸事件的处理方法 */ 381 public boolean onTouchEvent(MotionEvent ev) { 382 if (mVelocityTracker == null) { 383 mVelocityTracker = VelocityTracker.obtain(); 384 } 385 mVelocityTracker.addMovement(ev); 386 final int action = ev.getAction(); 387 switch (action) { 388 case MotionEvent.ACTION_DOWN: {//如果是down事件,记录住当前的x坐标 389 final float x = ev.getX(); 390 if (!mScroller.isFinished()) { 391 mScroller.abortAnimation(); 392 } 393 mLastMotionX = x; 394 break; 395 } 396 case MotionEvent.ACTION_MOVE: { 397 final float x = ev.getX(); 398 final float deltaX = x - mLastMotionX; 399 if (!mIsBeingDragged) {//如果还没有处于拖动,则判断两次的差值是否大于最小拖动的距离 400 if (Math.abs(deltaX) > mTouchSlop) { 401 mIsBeingDragged = true; 402 } 403 } 404 if (mIsBeingDragged) {//如果处于拖动状态,记录住x坐标 405 mLastMotionX = x; 406 onMove(deltaX); 407 } 408 break; 409 } 410 case MotionEvent.ACTION_UP: { 411 if (mIsBeingDragged) { 412 final VelocityTracker velocityTracker = mVelocityTracker; 413 //先对速度进行一个调整,第一个参数是时间单位,1000毫秒,第二个参数是最大速度。 414 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 415 float velocity = velocityTracker.getXVelocity();//获取水平方向上的速度 416 onUp(velocity); 417 } 418 } 419 case MotionEvent.ACTION_CANCEL: { 420 mIsBeingDragged = false; 421 if (mVelocityTracker != null) { 422 mVelocityTracker.recycle(); 423 mVelocityTracker = null; 424 } 425 break; 426 } 427 } 428 return true; 429 } 430 431 private void onMove(float x) { 432 if (mMaxScrollX <= 0) { 433 if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) { 434 mViewPager.fakeDragBy(x); 435 } 436 } else { 437 int scrollByX = -(int) (x + 0.5); 438 if (getScrollX() + scrollByX < 0) { 439 scrollByX = 0 - getScrollX(); 440 mLeftEdge.onPull(Math.abs(x) / getWidth()); 441 } 442 if (getScrollX() + scrollByX > mMaxScrollX) { 443 scrollByX = mMaxScrollX - getScrollX(); 444 mRightEdge.onPull(Math.abs(x) / getWidth()); 445 } 446 scrollBy(scrollByX, 0); 447 ViewCompat.postInvalidateOnAnimation(this); 448 } 449 } 450 451 private void onUp(float velocity) { 452 if (mMaxScrollX <= 0) { 453 if (mViewPager.isFakeDragging()) mViewPager.endFakeDrag(); 454 } else { 455 if (Math.abs(velocity) <= mMinimumVelocity) { 456 return; 457 } 458 mScroller.fling(getScrollX(), 0, -(int) (velocity + 0.5), 0, 0, mMaxScrollX, 0, 0, 270, 0); 459 ViewCompat.postInvalidateOnAnimation(this); 460 } 461 } 462 463 @Override 464 public void computeScroll() { 465 if (mScroller.computeScrollOffset()) { 466 int oldX = mLastScrollX; 467 mLastScrollX = mScroller.getCurrX(); 468 if (mLastScrollX < 0 && oldX >= 0) { 469 mLeftEdge.onAbsorb((int) mScroller.getCurrVelocity()); 470 } else if (mLastScrollX > mMaxScrollX && oldX <= mMaxScrollX) { 471 mRightEdge.onAbsorb((int) mScroller.getCurrVelocity()); 472 } 473 int x = mLastScrollX; 474 if (mLastScrollX < 0) { 475 x = 0; 476 } else if (mLastScrollX > mMaxScrollX) { 477 x = mMaxScrollX; 478 } 479 scrollTo(x, 0); 480 } 481 ViewCompat.postInvalidateOnAnimation(this); 482 } 483 484 /** 检测mIndicatorOffset的合法性,并计算出其他有关tab的属性值 */ 485 private void checkAndcalculate() { 486 // 如果指示器起始位置比第一个tab的起始位置还要小,纠正为第一个tab的起始位置,指示器宽度就是第一个tab的宽度 487 final View firstTab = getChildAt(0); 488 if (mIndicatorLeft < firstTab.getLeft()) { 489 mIndicatorLeft = firstTab.getLeft(); 490 mIndicatorWidth = firstTab.getWidth(); 491 } 492 // 如果指示器起始位置比最后一个tab的起始位置还要大,纠正为最后一个tab的起始位置,指示器宽度就是最后一个tab的宽度 493 View lastTab = getChildAt(mTabCount - 1); 494 if (mIndicatorLeft > lastTab.getLeft()) { 495 mIndicatorLeft = lastTab.getLeft(); 496 mIndicatorWidth = lastTab.getWidth(); 497 } 498 // 通过指示器的起始位置计算出当前处于第几个position,并且计算出已经偏移了多少,偏移量是以当前所处的tab的宽度的百分比 499 for (int i = 0; i < mTabCount; i++) { 500 View tab = getChildAt(i); 501 if (mIndicatorLeft < tab.getLeft()) { 502 mCurrentPosition = i - 1; 503 View currentTab = getChildAt(mCurrentPosition); 504 mCurrentOffsetPixels = (mIndicatorLeft - currentTab.getLeft()) / (currentTab.getWidth() + 0.0f); 505 break; 506 } 507 } 508 } 509 510 /** 滚动到指定的child */ 511 public void scrollSelf(int position, float offset) { 512 if (position >= mTabCount) { 513 return; 514 } 515 final View tab = getChildAt(position); 516 mIndicatorLeft = (int) (tab.getLeft() + tab.getWidth() * offset + 0.5); 517 int rightPosition = position + 1; 518 if (offset > 0 && rightPosition < mTabCount) { 519 View rightTab = getChildAt(rightPosition); 520 mIndicatorWidth = (int) (tab.getWidth() * (1 - offset) + rightTab.getWidth() * offset + 0.5); 521 } else { 522 mIndicatorWidth = tab.getWidth(); 523 } 524 checkAndcalculate(); 525 526 int newScrollX = position * mSplitScrollX + (int) (offset * mSplitScrollX + 0.5); 527 if (newScrollX < 0) { 528 newScrollX = 0; 529 } 530 if (newScrollX > mMaxScrollX) { 531 newScrollX = mMaxScrollX; 532 } 533 //scrollTo(newScrollX, 0);//滑动 534 int duration = 100; 535 if (mSelectedPosition != -1) { 536 duration = (Math.abs(mSelectedPosition - position)) * 100; 537 } 538 mScroller.startScroll(getScrollX(), 0, (newScrollX - getScrollX()), 0, duration); 539 ViewCompat.postInvalidateOnAnimation(this); 540 } 541 542 /** 选中指定位置的Tab */ 543 private void selectTab(int position) { 544 for (int i = 0; i < mTabCount; i++) { 545 View tab = getChildAt(i); 546 if (tab != null) { 547 tab.setSelected(position == i); 548 } 549 } 550 } 551 552 /** ViewPager的OnPageChangeListener实现类,因为我们需要在PagerTab中获取PagerView的监听,以便可以调整tab */ 553 private class PageListener implements OnPageChangeListener { 554 @Override 555 public void onPageScrolled(int position, float positionOffset, final int positionOffsetPixels) { 556 //根据VierPager的偏移值来滚动tab 557 scrollSelf(position, positionOffset); 558 if (mDelegatePageListener != null) {//这个是提供给外部的 559 mDelegatePageListener.onPageScrolled(position, positionOffset, positionOffsetPixels); 560 } 561 } 562 563 @Override 564 public void onPageScrollStateChanged(int state) { 565 if (state == ViewPager.SCROLL_STATE_IDLE) { 566 mSelectedPosition = -1; 567 } 568 if (mDelegatePageListener != null) { 569 mDelegatePageListener.onPageScrollStateChanged(state); 570 } 571 } 572 573 @Override 574 public void onPageSelected(int position) { 575 System.out.println("onPageSelected:" + position); 576 mSelectedPosition = position; 577 selectTab(position); 578 if (mDelegatePageListener != null) { 579 mDelegatePageListener.onPageSelected(position); 580 } 581 } 582 } 583 584 /** 如果指示器希望是图片,则继承该接口 */ 585 public interface IconTabProvider { 586 public int getPageIconResId(int position); 587 public int getPageSelectedIconResId(); 588 } 589 }
【修改1】新建类BaseActivity.java继承与Activity;

[添加v7的兼容包]该包可以兼容2.x版本的android系统;其实在android4.x版本已经支持了actionBar;但是为了兼容其他2.x以上的android系统,需要增加此包;




【修改2】 状态选择器;


[新建背景图片选择器]

[新建文字颜色选择器]选中和按下的颜色都会改变;

【修改3】导包

【修改4】缺少此方法,增加此方法;


【使用该自定义的控件】使用自定义的TapPager控件必须设置其背景,否则会运行报错;



6.主页面框架搭建
【说明】MainActivity继承BaseActivity,可以拥有ActionBar的效果;

【增加id】

6.1 VIewPager中的页面的添加
【说明】在ViewPager中存在7个页面,每个页面都是一个fragment;
【fragment的数据适配器】该适配器其实是ViewPager的子类,重写的方法比较少;

【适配器数据的设置/指针和页面的绑定】绑定:指示器和下面的页面中的ViewPager的绑定;

【返回页签的标题】





6.2 生产fragment的工厂
【新建factory类】专门生产fragment的类;

【新建基类】新建一个fragment的基类,抽取共同的特性

【添加各自的fragment】完善factory类


【源码】思想:没有必要重复建立fragment,可以使用集合放入之后循环使用,提高性能;
1 package com.itheima.googleplay74.ui.fragment; 2 3 import java.util.HashMap; 4 5 /** 6 * 生产fragment工厂 7 * 8 * @author Kevin 9 * @date 2015-10-27 10 */ 11 public class FragmentFactory { 12 13 private static HashMap<Integer, BaseFragment> mFragmentMap = new HashMap<Integer, BaseFragment>(); 14 15 public static BaseFragment createFragment(int pos) { 16 // 先从集合中取, 如果没有,才创建对象, 提高性能 17 BaseFragment fragment = mFragmentMap.get(pos); 18 19 if (fragment == null) { 20 switch (pos) { 21 case 0: 22 fragment = new HomeFragment(); 23 break; 24 case 1: 25 fragment = new AppFragment(); 26 break; 27 case 2: 28 fragment = new GameFragment(); 29 break; 30 case 3: 31 fragment = new SubjectFragment(); 32 break; 33 case 4: 34 fragment = new RecommendFragment(); 35 break; 36 case 5: 37 fragment = new CategoryFragment(); 38 break; 39 case 6: 40 fragment = new HotFragment(); 41 break; 42 43 default: 44 break; 45 } 46 47 mFragmentMap.put(pos, fragment);// 将fragment保存在集合中 48 } 49 50 return fragment; 51 } 52 }
6.3 适配器中数据返回fragment

6.4 BUG


【原因】

【解决方法】设置亮色的主题

【运行效果】临时测试写了下面的显示TextView;


7.LoadingPage-加载中布局
7.1 标签页中的共同的内容
【加载失败】页面都一致;

【加载中的状态】具有一个进度条,页面都一致;

【加载数据为空的页面】服务器没有数据的话,显示的页面是一致的;
7.2 在BaseActivity中构建共同的页面
【思路】共同的页面显示的内容就是下面蓝框显示的区域,可以自定义一个帧布局,根据不同的情况,显示不同的加载的页面;

【新建加载类】

【自定义加载的状态】

7.3【初始化加载中的布局】

【加载中的布局文件】

【初始化加载中的布局】

【加载中的布局的给标签页的设置分配】

【效果】因为在父类中设置了正在加载的布局,因此在其他的标签页面都会显示加载中的页面的效果;

8.自定义进度条
【效果】自定义进度条,黑白相间的转动

【原理】使用两张图片同时进行旋转,方向相反;

【代码书写】


【progressBar设置动画】

【效果】比较单调,效果一直是重复;

【效果改进】同样时间,一方跑的比较快,跑的角度要大;

【效果】
9.LoadingPage-加载失败布局
【效果】

【布局】

【button的状态选择器】




【帧布局中放入加载失败的布局】
10.数据为空的布局的加载





11.LoadingPage-根据状态显示页面

【效果】切换当前的状态值,可以分别查看显示的布局的样式;
12.onCreateSuccessView实现
【说明】加载成功之后的各个页面的内容不一致,需要写一个抽象的方法,让各个页面的子类实现该抽象方法,
进而加载不同的页面内容;

【说明】注意同名方法的调用,容易栈溢出;



【总结】实现的关系

13.LoadingPage-异步加载数据的封装
13.1 加载数据成功及加载数据返回状态
【初始化数据initData】

【改方法名initData改为onLoad】


【基类onLoad方法】


【子类实现基类的onLoad方法】

【总结】

【枚举的使用】相当于创建新的对象;


[枚举当中可以带有构造方法,带有参数]

【子类数据请求之后返回的值】开启子线程,加载数据,加载数据根据返回的状态,重新刷新显示页面;



【更新UI需要放到主线程】调用之前写好的UIUtils的方法,将加载数据更新UI切换到主线程中;

13.2 加载数据onLoad的方法的调用
【说明】【加载数据的时机】在页面切换时加载数据,因此需要监听;



【总结】

13.3 子类标签页网络请求数据
【说明】本身子类HomeFragment调用 LoadingPage加载数据的时候LoadData的时候已经在子线程中了;
因此,在子类标签页网络加载数据的时候没有必要再次开辟子线程了;

【子类返回数据加载成功】【子类的数据加载成功之后的调用方法也是在主线程中】

【测试】应用界面设置的状态是成功;应用设置的状态是失败;游戏界面设置的状态是没有数据;

14. 总结-参看视频
【说明】此篇内容是UI框架的搭建;
浙公网安备 33010602011771号