編輯:關於android開發
年後開始上班甚是清閒,所以想搗鼓一些東西。在翻閱大神傑作Android 教你打造炫酷的ViewPagerIndicator 不僅僅是高仿MIUI的時候看到下面有一條評論說,如果導航欄能滑動就更好了。我就想我可以去改一下就可以。然後又想感覺有點像UC的頭條的界面。於是就往裡面加東西,調試寫代碼。弄了兩天有點效果了,寫出來看看了。
項目下載地址:http://download.csdn.net/detail/qq_16064871/9434291
用咔咔大師錄屏的gif效果,有點失真。
這是第一個版本的效果。因為我不斷往裡面加東西,所以有幾個版本了。
實現思路就是自定義繪制了。主要有兩層,第一層是ViewPagerIndicator。主要負責導航欄的三角形指示器的繪制,以及頁面滑動的回調,控制。當然這裡需要android.support.v4.view.ViewPager這東西配合使用。
第二層是,導航欄的滑動效果,以及最左、最右有 反彈的效果。這個效果我是從以前一篇博文改動到了這裡來。鏈接:android 滾動條下拉反彈的效果(類似微信朋友圈)。這裡效果是垂直,改為橫向就行了。還有這兩層同時使用需要處理就是滑動不要沖突就可以了。
xml布局代碼:
<com.ucnew.view.bouncescrollview android:id="@+id/id_scrollview" android:layout_width="0dp" android:layout_height="45dp" android:layout_weight="1" android:focusableintouchmode="false" android:scrollbars="none"> <com.ucnew.view.viewpagerindicator android:id="@+id/id_indicator" android:layout_width="match_parent" android:layout_height="45dp" android:orientation="horizontal" mmsx:item_count="4"> </com.ucnew.view.viewpagerindicator> </com.ucnew.view.bouncescrollview>
ViewPagerIndicator
package com.ucnew.view; import java.util.List; import com.ucnew.activity.R; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.CornerPathEffect; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Path; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.Gravity; import android.view.View; import android.view.WindowManager; import android.widget.LinearLayout; import android.widget.TextView; public class ViewPagerIndicator extends LinearLayout { //繪制三角形的畫筆 private Paint mPaint; //path構成一個三角形 private Path mPath; //三角形的寬度 private int mTriangleWidth; // 三角形的高度 private int mTriangleHeight; //三角形的寬度為單個Tab的1/6 private static final float RADIO_TRIANGEL = 1.0f / 6; // 三角形的最大寬度 private final int DIMENSION_TRIANGEL_WIDTH = (int) (getScreenWidth() / 3 * RADIO_TRIANGEL); //初始時,三角形指示器的偏移量 private int mInitTranslationX; // 手指滑動時的偏移量 private float mTranslationX; // tab數量 private int mTabVisibleCount; // tab上的內容 private ListmTabTitles; // 與之綁定的ViewPager public ViewPager mViewPager; // 標題正常時的顏色 private static final int COLOR_TEXT_NORMAL = 0x77FFFFFF; //標題選中時的顏色 private static final int COLOR_TEXT_HIGHLIGHTCOLOR = 0xFFFFFFFF; public ViewPagerIndicator(Context context) { this(context, null); } public ViewPagerIndicator(Context context, AttributeSet attrs) { super(context, attrs); // 獲得自定義屬性,tab的數量 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewPagerIndicator); mTabVisibleCount = a.getInt(R.styleable.ViewPagerIndicator_item_count,4); if (mTabVisibleCount < 0) mTabVisibleCount = 4; a.recycle(); // 初始化畫筆 mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(Color.parseColor("#ffffffff")); mPaint.setStyle(Style.FILL); mPaint.setPathEffect(new CornerPathEffect(3)); } //繪制指示器 @Override protected void dispatchDraw(Canvas canvas) { canvas.save(); // 畫筆平移到正確的位置 canvas.translate(mInitTranslationX + mTranslationX, getHeight() + 1); canvas.drawPath(mPath, mPaint); canvas.restore(); super.dispatchDraw(canvas); } //初始化三角形的寬度 @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mTriangleWidth = (int) (w / mTabVisibleCount * RADIO_TRIANGEL);// 1/6 of // width mTriangleWidth = Math.min(DIMENSION_TRIANGEL_WIDTH, mTriangleWidth); // 初始化三角形 initTriangle(); // 初始時的偏移量 mInitTranslationX = getScreenWidth() / mTabVisibleCount / 2 - mTriangleWidth / 2; } //設置可見的tab的數量 public void setVisibleTabCount(int count) { this.mTabVisibleCount = count; } //設置tab的標題內容 可選,生成textview加入布局,靈活處理 public void setTabItemTitles(List datas) { // 如果傳入的list有值,則移除布局文件中設置的view if (datas != null && datas.size() > 0) { this.removeAllViews(); this.mTabTitles = datas; for (String title : mTabTitles) { // 添加view addView(generateTextView(title)); } // 設置item的click事件 setItemClickEvent(); } } //根據標題生成我們的TextView private TextView generateTextView(String text) { TextView tv = new TextView(getContext()); LinearLayout.LayoutParams lp = new LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); lp.width = getScreenWidth() / mTabVisibleCount; tv.setGravity(Gravity.CENTER); tv.setTextColor(COLOR_TEXT_NORMAL); tv.setText(text); tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); tv.setLayoutParams(lp); return tv; } //對外的ViewPager的回調接口 public interface PageChangeListener { public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels); public void onPageSelected(int position); public void onPageScrollStateChanged(int state); } // 對外的ViewPager的回調接口 private PageChangeListener onPageChangeListener; // 對外的ViewPager的回調接口的設置 public void setOnPageChangeListener(PageChangeListener pageChangeListener) { this.onPageChangeListener = pageChangeListener; } // 設置關聯的ViewPager,以及傳入 BounceScrollView,進行設置滾動 public void setViewPager(ViewPager mViewPager, final BounceScrollView scrollView, int pos) { this.mViewPager = mViewPager; mViewPager.setOnPageChangeListener(new OnPageChangeListener() { @Override public void onPageSelected(int position) { // 設置字體顏色高亮 resetTextViewColor(); highLightTextView(position); // 回調 if (onPageChangeListener != null) { onPageChangeListener.onPageSelected(position); } } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { // 滾動 scroll(scrollView,position, positionOffset); // 回調 if (onPageChangeListener != null) { onPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels); } } @Override public void onPageScrollStateChanged(int state) { // 回調 if (onPageChangeListener != null) { onPageChangeListener.onPageScrollStateChanged(state); } } }); // 設置當前頁 mViewPager.setCurrentItem(pos); // 高亮 highLightTextView(pos); } //高亮文本 protected void highLightTextView(int position) { View view = getChildAt(position); if (view instanceof TextView) { ((TextView) view).setTextColor(COLOR_TEXT_HIGHLIGHTCOLOR); } } //重置文本顏色 private void resetTextViewColor() { for (int i = 0; i < getChildCount(); i++) { View view = getChildAt(i); if (view instanceof TextView) { ((TextView) view).setTextColor(COLOR_TEXT_NORMAL); } } } // 設置點擊事件 public void setItemClickEvent() { int cCount = getChildCount(); for (int i = 0; i < cCount; i++) { final int j = i; View view = getChildAt(i); view.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mViewPager.setCurrentItem(j); } }); } } // 初始化三角形指示器 private void initTriangle() { mPath = new Path(); mTriangleHeight = (int) (mTriangleWidth / 2 / Math.sqrt(2)); mPath.moveTo(0, 0); mPath.lineTo(mTriangleWidth, 0); mPath.lineTo(mTriangleWidth / 2, -mTriangleHeight); mPath.close(); } //指示器跟隨手指滾動,以及容器滾動 public void scroll(BounceScrollView scrollView,int position, float offset) { // 不斷改變偏移量,invalidate mTranslationX = getScreenWidth() / mTabVisibleCount * (position + offset); int tabWidth = getScreenWidth() / mTabVisibleCount; // 容器滾動,當移動到倒數最後一個的時候,開始滾動 if (offset > 0 && position >= (mTabVisibleCount - 1) && getChildCount() > mTabVisibleCount) { if (mTabVisibleCount != 1) { //下面注釋掉,是滾動ViewPagerIndicator這個LinearLayout // this.scrollTo((position - (mTabVisibleCount - 1)) * tabWidth + (int) (tabWidth * offset), 0); //只滾動滾動條,禁止滾動lineayout scrollView.setScrolledTo((position - (mTabVisibleCount - 1)) * tabWidth + (int) (tabWidth * offset), 0); } else // 為count為1時 的特殊處理 { this.scrollTo(position * tabWidth + (int) (tabWidth * offset), 0); // scrollView.setScrolledTo(position * tabWidth + (int) (tabWidth * offset), 0); } } //處理特殊情況 else if (offset > 0 && position <= mTabVisibleCount - 1) { scrollView.setScrolledTo(0, 0); } invalidate(); } //設置布局中view的一些必要屬性;如果設置了setTabTitles,布局中view則無效 @Override protected void onFinishInflate() { super.onFinishInflate(); int cCount = getChildCount(); if (cCount == 0) return; for (int i = 0; i < cCount; i++) { View view = getChildAt(i); LinearLayout.LayoutParams lp = (LayoutParams) view .getLayoutParams(); lp.weight = 0; lp.width = getScreenWidth() / mTabVisibleCount; view.setLayoutParams(lp); } // 設置點擊事件 setItemClickEvent(); } //獲得屏幕的寬度 public int getScreenWidth() { WindowManager wm = (WindowManager) getContext().getSystemService( Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetrics); return outMetrics.widthPixels ; } }
package com.ucnew.view; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.animation.TranslateAnimation; import android.widget.HorizontalScrollView; /** * ScrollView反彈效果的實現 */ public class BounceScrollView extends HorizontalScrollView { // 孩子View private View inner; // 點擊時x坐標 private float x; // 矩形(這裡只是個形式,只是用於判斷是否需要動畫 private Rect normal = new Rect(); // 是否開始計算 private boolean isCount = false; private RotatImageView mRotatImageView; public BounceScrollView(Context context, AttributeSet attrs) { super(context, attrs); } /*** * 根據 XML 生成視圖工作完成.該函數在生成視圖的最後調用,在所有子視圖添加完之後. 即使子類覆蓋了 onFinishInflate * 方法,也應該調用父類的方法,使該方法得以執行. */ @Override protected void onFinishInflate() { if (getChildCount() > 0) { inner = getChildAt(0); } } //手動需要設置滾動位置 public void setScrolledTo(int position, float positionOffset) { this.scrollTo(position,(int) positionOffset); } //監聽touch @Override public boolean onTouchEvent(MotionEvent ev) { if (inner != null) { commOnTouchEvent(ev); } return super.onTouchEvent(ev); } //觸摸事件 public void commOnTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_UP: // 手指松開. if (isNeedAnimation()) { animation(); isCount = false; } break; /*** * 排除出第一次移動計算,因為第一次無法得知y坐標, 在MotionEvent.ACTION_DOWN中獲取不到, * 因為此時是MyScrollView的touch事件傳遞到到了LIstView的孩子item上面.所以從第二次計算開始. * 然而我們也要進行初始化,就是第一次移動的時候讓滑動距離歸0. 之後記錄准確了就正常執行. */ case MotionEvent.ACTION_MOVE: final float preX = x;// 按下時的y坐標 float nowX = ev.getX();// 時時y坐標 int deltaX = (int) (preX - nowX);// 滑動距離 if (!isCount) { deltaX = 0; // 在這裡要歸0. } x = nowX; // 當滾動到最上或者最下時就不會再滾動,這時移動布局 if (isNeedMove()) { // 初始化頭部矩形 if (normal.isEmpty()) { // 保存正常的布局位置 normal.set(inner.getLeft(), inner.getTop(), inner.getRight(), inner.getBottom()); } // 移動布局 inner.layout(inner.getLeft() - deltaX / 4, inner.getTop(), inner.getRight() - deltaX / 4, inner.getBottom()); //圖片加號旋轉,如果不需要這個直接刪了就行 if (mRotatImageView != null) { mRotatImageView.setRotationLeft(); } } isCount = true; break; default: break; } } //回縮動畫 public void animation() { // 開啟移動動畫 TranslateAnimation ta = new TranslateAnimation(0, 0, inner.getTop(), normal.top); ta.setDuration(200); inner.startAnimation(ta); // 設置回到正常的布局位置 inner.layout(normal.left, normal.top, normal.right, normal.bottom); normal.setEmpty(); } //設置圖片加號旋轉 public void setRotatImageView(RotatImageView rotatImageView){ this.mRotatImageView = rotatImageView; } // 是否需要開啟動畫 public boolean isNeedAnimation() { return !normal.isEmpty(); } /*** * 是否需要移動布局 inner.getMeasuredHeight():獲取的是控件的總高度 * getHeight():獲取的是屏幕的高度 */ public boolean isNeedMove() { int offset = inner.getMeasuredWidth() - getWidth(); int scrollX = getScrollX(); // 0是頂部反彈 //是底部反彈加上 if (scrollX == 0 || scrollX == offset) { return true; } return false; } }
那麼就直接看下一個版本。
多了一個圖標,這個圖標可以用來監聽,等等。
我後面搗鼓加了一個旋轉動畫,動畫效果可能不是很好。但也可以看看。
有一些自定義屬性以及效果的代碼就不貼,感興趣下載源碼。看不懂的,看我前幾篇文章,自定義控件使用。
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:mmsx="http://schemas.android.com/apk/res/com.ucnew.activity" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffffff" android:orientation="vertical"> <linearlayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#8000bf5f"> <com.ucnew.view.bouncescrollview android:id="@+id/id_scrollview" android:layout_width="0dp" android:layout_height="45dp" android:layout_weight="1" android:focusableintouchmode="false" android:scrollbars="none"> <com.ucnew.view.viewpagerindicator android:id="@+id/id_indicator" android:layout_width="match_parent" android:layout_height="45dp" android:orientation="horizontal" mmsx:item_count="4"> </com.ucnew.view.viewpagerindicator> </com.ucnew.view.bouncescrollview> <com.ucnew.view.rotatimageview android:id="@+id/id_rotat_imageView" android:layout_width="50dp" android:layout_height="wrap_content" android:paddingleft="30dp" android:layout_gravity="center" mmsx:src="@drawable/add"> </com.ucnew.view.rotatimageview> </linearlayout> <android.support.v4.view.viewpager android:id="@+id/id_vp" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"> </android.support.v4.view.viewpager> </linearlayout>
ViewPagerIndicator類的這個函數
//獲得屏幕的寬度 public int getScreenWidth() { WindowManager wm = (WindowManager) getContext().getSystemService( Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetrics); //獲取是整個屏幕的寬度,我試過自定義寬度測量寬度,不行。因為本身還沒內容,是後面添加的。所以需要後面加東西 //需要這裡減去寬度就行。其中這裡減去120就是60dp的寬度 return outMetrics.widthPixels -100; }
package com.ucnew.view; import com.ucnew.activity.R; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PaintFlagsDrawFilter; import android.util.AttributeSet; import android.util.Log; import android.view.View; public class RotatImageView extends View { private Paint paint = null; // 畫筆 private Bitmap mbitmap = null; // 圖片位圖 private Bitmap bitmapDisplay = null; private Matrix matrix = null; private int mWidth = 0; // 圖片的寬度 private int mHeight = 0; // 圖片的高度 private float fAngle = 180.0f; // 圖片旋轉 private PaintFlagsDrawFilter mDrawFilter; public RotatImageView(Context context) { super(context); } public RotatImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * 初始化一些自定義的參數 * * @param context * @param attrs * @param defStyle */ public RotatImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.RotatImageView, defStyle, 0); int n = a.getIndexCount(); for (int i = 0; i < n; i++) { int attr = a.getIndex(i); switch (attr) { // 原始圖片,在布局裡面獲取 case R.styleable.RotatImageView_src: mbitmap = BitmapFactory.decodeResource(getResources(),a.getResourceId(attr, 0)); bitmapDisplay = mbitmap; break; } } a.recycle(); mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG); paint = new Paint(); paint.setFlags(Paint.ANTI_ALIAS_FLAG); matrix = new Matrix(); } /** * 計算控件的高度和寬度 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 設置寬度 int specMode = MeasureSpec.getMode(widthMeasureSpec); int specSize = MeasureSpec.getSize(widthMeasureSpec); //match_parent或者設置的精確值獲取 //MeasureSpec.EXACTLY if (specMode == MeasureSpec.EXACTLY) { mWidth = specSize; } else { // 由圖片決定的寬 //getPaddingLeft(),getPaddingRight()這兩個值是控件屬性的向內偏移的距離值,所以的一起計算 //區別於layout_marginLeft,兩個控件的左間距值設置 int desireByImg = getPaddingLeft() + getPaddingRight() + mbitmap.getWidth(); // wrap_content if (specMode == MeasureSpec.AT_MOST) { //所以最小的值,寬度的話是左右內偏移距離之和 mWidth = Math.min(desireByImg, specSize); } else mWidth = desireByImg; } // 設置高度,部分解釋同上 specMode = MeasureSpec.getMode(heightMeasureSpec); specSize = MeasureSpec.getSize(heightMeasureSpec); //match_parent或者設置的精確值獲取 //MeasureSpec.EXACTLY if (specMode == MeasureSpec.EXACTLY) { mHeight = specSize; } else { int desire = getPaddingTop() + getPaddingBottom() + mbitmap.getHeight(); // wrap_content if (specMode == MeasureSpec.AT_MOST) { mHeight = Math.min(desire, specSize); } else mHeight = desire; } //計算好的寬度以及高度是值,設置進去 setMeasuredDimension(mWidth, mHeight); } // 向左旋轉 public void setRotationLeft() { fAngle = fAngle - 20; setAngle(); } // 向右旋轉 public void setRotationRight() { fAngle = fAngle + 20; setAngle(); } private boolean isRoate = false; // 設置旋轉比例 private void setAngle() { Log.i("Show", String.valueOf(fAngle)); isRoate = true; //設置圖片的旋轉中心,即繞(X,Y)這點進行中心旋轉 要旋轉的角度 matrix.preRotate(fAngle, (float)mbitmap.getWidth()/2, (float)mbitmap.getHeight()/2); invalidate(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //消除鋸齒, 圖片旋轉後的鋸齒消除不成功,實在不行圖片邊緣加一些白色像素點 canvas.setDrawFilter(mDrawFilter); if (isRoate) { canvas.drawBitmap(bitmapDisplay, matrix, paint); isRoate = false; }else { canvas.drawBitmap(bitmapDisplay, 0, 0, paint); } } }
package com.ucnew.activity; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import com.ucnew.activity.R; import com.ucnew.fragment.VpSimpleFragment; import com.ucnew.view.BounceScrollView; import com.ucnew.view.RotatImageView; import com.ucnew.view.ViewPagerIndicator; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import android.view.Window; public class MainActivity extends FragmentActivity { private ListmTabContents = new ArrayList (); private FragmentPagerAdapter mAdapter; private ViewPager mViewPager; private List mDatas = Arrays.asList("頁面1", "頁面2", "頁面3", "頁面4", "頁面5", "頁面6", "頁面7", "頁面8", "頁面9"); private ViewPagerIndicator mIndicator; private BounceScrollView mScrollView; private RotatImageView mRotatImageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.vp_indicator); initView(); initDatas(); //設置Tab上的標題 mIndicator.setTabItemTitles(mDatas); mViewPager.setAdapter(mAdapter); //設置關聯的ViewPager mIndicator.setViewPager(mViewPager,mScrollView,0); //設置關聯的圖片旋轉,根據需要設置,效果不是很好 mScrollView.setRotatImageView(mRotatImageView); } private void initDatas() { for (String data : mDatas) { VpSimpleFragment fragment = VpSimpleFragment.newInstance(data); mTabContents.add(fragment); } mAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) { @Override public int getCount() { return mTabContents.size(); } @Override public Fragment getItem(int position) { return mTabContents.get(position); } }; } private void initView() { mScrollView = (BounceScrollView) findViewById(R.id.id_scrollview); mViewPager = (ViewPager) findViewById(R.id.id_vp); mIndicator = (ViewPagerIndicator) findViewById(R.id.id_indicator); mRotatImageView = (RotatImageView)findViewById(R.id.id_rotat_imageView); } }
還有部分代碼沒貼。自已下載查看吧。
錄屏不是很清晰,來一張截圖,夠清晰的
上一節講了使用WebView控件訪問網絡的方法,而除這種方法之外,我們還可以使
Android學習之旅:五子棋,android之旅五子棋 在學完了Android的基礎之後,我開始嘗試著寫一些小項目練練手,同時進一步鞏固自己的基礎知識,而我選的的第一
Android Studio快捷鍵指南(本文持續更新) 這是我在使用Android Studio過程中接觸到的一些快捷鍵,和大家分享,後面會繼續完善此文,也歡迎大家踴
Android 代碼動態改變View的屬性 設置Android View的長寬和位置我們平時都會在Layout的XML中定義,那麼什麼時候需要動態在代碼中設置View