一、前言
有一個月左右沒有更新部落格了,最近在做一個新專案,真的是忙得不可開交,已經進入了996模式,沒有什麼空餘的時間來寫部落格。雖然開始一個新的專案很忙很累,但是確實也是一個提升自己的機會,在之前的文章《Android開發者的進階之路》中我也說過,其實最好的進階來自於實戰,將學到的這些新技術、新框架用於專案中,不斷的採坑、填坑。這樣我們能收穫更多,也就成長的更快。
技術的沉澱來自於總結,感覺專案中又有好多的東西可以總結了,後續我會將其中用到的一些技術總結出來分享給大家,歡迎大家持續關注。好了,閒話不多說,今天就開始第一篇,關於弧形HeaderView。
二、需求
這種弧形的Header介面最近很流行,在幾個APP中都有看到,先來看兩張圖。
掌盟的Profile頁面:
LifeSum 中的部分頁面:
好了,就是上面這兩種效果:
1, 靜態的弧形header
2, 弧形ViewPager
思路:
怎麼做出上面的兩種效果呢?其實說是兩種效果,其實只有一種效果,那就是靜態弧形Header View ,搞定了這個,後面的ViewPager效果就簡單了。
弧形ViewPager,我們可以把它拆成2部分:
1 . 弧形Header View 做為背景。
2 . 透明的普通ViewPager.
重點來了,這個弧形的Header View 怎麼來做呢?我們通過自定義View 來做,我們再來仔細看一下這個View:
由兩部分組成:矩形+弧形,上面的矩形繪製很簡單,不多說。下面的弧形,可以通過二階貝塞爾曲線來繪製。
三、自定義 ArcHeaderView
通過上面的分析,我們知道它就是一個 矩形 + 一條弧線,我們可以通過Path 來組合兩種形狀。最終繪製出我們所要的形狀。
二階貝塞爾曲線:
首先我們來看一下二階貝塞爾曲線。補充一下二階貝塞爾曲線的知識點:
二階曲線由兩個資料點(A 和 C),一個控制點(B)來描述曲線狀態,大致如下:
上圖中紅色曲線部分就是傳說中的二階貝塞爾曲線,那麼這條紅色曲線是如何生成的呢?接下來我們就以其中的一個狀態分析一下:
連線AB BC,並在AB上取點D,BC上取點E,使其滿足條件:
連線DE,取點F,使得:
這樣獲取到的點F就是貝塞爾曲線上的一個點,動態過程如下:
繪製一段二階貝塞爾曲線需要三個點:一個起始點,一個終點和一個控制點。控制點的位置不同,決定了曲線的彎曲程度不同。繪製二階貝塞爾曲線的方法為:quadTo(float x1, float y1, float x2, float y2)
繪製程式碼如下:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
mPath.reset();
// 上半部分矩形
mPath.moveTo(0, 0);
mPath.addRect(0, 0, mWidth, mHeight - mArcHeight, Path.Direction.CCW);
// 起始點
mStartPoint.x = 0;
mStartPoint.y = mHeight - mArcHeight;
// 終點
mEndPoint.x = mWidth;
mEndPoint.y = mHeight - mArcHeight;
// 控制點
mControlPoint.x = mWidth / 2 - 50;
mControlPoint.y = mHeight + 100;
// 初始化shader
mLinearGradient = new LinearGradient(mWidth / 2, 0, mWidth / 2, mHeight, mStartColor, mEndColor, Shader.TileMode.MIRROR);
invalidate();
}複製程式碼
最後再onDraw 方法中,直接呼叫方法繪製就好。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setShader(mLinearGradient);
mPath.moveTo(mStartPoint.x, mStartPoint.y);
// 新增貝塞爾曲線
mPath.quadTo(mControlPoint.x, mControlPoint.y, mEndPoint.x, mEndPoint.y);
// 繪製
canvas.drawPath(mPath, mPaint);
}複製程式碼
最終效果如下:
ArcHeaderView
的全部程式碼如下:
public class ArcHeaderView extends View {
private Paint mPaint;
private PointF mStartPoint, mEndPoint, mControlPoint;
private int mWidth;
private int mHeight;
private Path mPath = new Path();
private int mArcHeight = 100;// 圓弧高度
private int mStartColor;
private int mEndColor;
private LinearGradient mLinearGradient;
public ArcHeaderView(Context context) {
super(context);
init();
}
public ArcHeaderView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public ArcHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public ArcHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
mPaint = new Paint();
// mPaint.setColor(Color.parseColor("#37B99F"));
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(10);
mPaint.setStyle(Paint.Style.FILL);
mStartPoint = new PointF(0, 0);
mEndPoint = new PointF(0, 0);
mControlPoint = new PointF(0, 0);
mStartColor = Color.parseColor("#FF3A80");
mEndColor = Color.parseColor("#FF3745");
}
/**
*
* @param startColor
* @param endColor
*/
public void setColor(@ColorInt int startColor, @ColorInt int endColor) {
mStartColor = startColor;
mEndColor = endColor;
mLinearGradient = new LinearGradient(mWidth / 2, 0, mWidth / 2, mHeight, mStartColor, mEndColor, Shader.TileMode.MIRROR);
invalidate();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
mPath.reset();
mPath.moveTo(0, 0);
mPath.addRect(0, 0, mWidth, mHeight - mArcHeight, Path.Direction.CCW);
mStartPoint.x = 0;
mStartPoint.y = mHeight - mArcHeight;
mEndPoint.x = mWidth;
mEndPoint.y = mHeight - mArcHeight;
mControlPoint.x = mWidth / 2 - 50;
mControlPoint.y = mHeight + 100;
// 初始化shader
mLinearGradient = new LinearGradient(mWidth / 2, 0, mWidth / 2, mHeight, mStartColor, mEndColor, Shader.TileMode.MIRROR);
///SweepGradient sweepGradient = new SweepGradient(mEndPoint.x / 2,mEndPoint.y / 2,mStartColor,mEndColor);
//mPaint.setShader(sweepGradient);
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setShader(mLinearGradient);
mPath.moveTo(mStartPoint.x, mStartPoint.y);
mPath.quadTo(mControlPoint.x, mControlPoint.y, mEndPoint.x, mEndPoint.y);
canvas.drawPath(mPath, mPaint);
}
}複製程式碼
四、弧形ViewPager 效果
有了上面的自定義ArcHeaderView ,要做這個ViewPager 的效果比較簡單。前面已經分析,這裡不贅述。直接上程式碼:
佈局檔案如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.zhouwei.androidtrainingsimples.arc_header_view.ArcHeaderView
android:id="@+id/header_view"
android:layout_width="match_parent"
android:layout_height="300dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="50dp"
>
<com.zhouwei.mzbanner.MZBannerView
android:id="@+id/arc_view_pager"
android:layout_width="match_parent"
android:layout_height="250dp"
app:canLoop="false"
app:open_mz_mode="false"
>
</com.zhouwei.mzbanner.MZBannerView>
</LinearLayout>
</FrameLayout>複製程式碼
Activity 程式碼如下:
public class ArcHeaderViewPagerActivity extends AppCompatActivity {
private ArcHeaderView mArcHeaderView;
private MZBannerView mMZBannerView;
public int mStartColor[] ;
public int mEndColor[] ;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.arc_header_viewpager_layout_activity);
StatusBarUtils.setColor(this,getResources().getColor(R.color.start_color),0);
initView();
}
private void initView() {
mStartColor = new int[]{getResources().getColor(R.color.start_color)
,getResources().getColor(R.color.page1_start_color)
,getResources().getColor(R.color.page2_start_color)
,getResources().getColor(R.color.page3_start_color)};
mEndColor = new int[]{getResources().getColor(R.color.end_color)
,getResources().getColor(R.color.page1_end_color)
,getResources().getColor(R.color.page2_end_color)
,getResources().getColor(R.color.page3_end_color)};
mMZBannerView = (MZBannerView) findViewById(R.id.arc_view_pager);
mArcHeaderView = (ArcHeaderView) findViewById(R.id.header_view);
mArcHeaderView.setColor(getResources().getColor(R.color.start_color),getResources().getColor(R.color.end_color));
mMZBannerView.addPageChangeLisnter(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
StatusBarUtils.setColor(ArcHeaderViewPagerActivity.this,mStartColor[position],0);
mArcHeaderView.setColor(mStartColor[position],mEndColor[position]);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
mMZBannerView.setPages(mockData(), new MZHolderCreator() {
@Override
public MZViewHolder createViewHolder() {
return new BannerViewHolder();
}
});
}
public static class BannerViewHolder implements MZViewHolder<MyData> {
private TextView text1,text2;
@Override
public View createView(Context context) {
// 返回頁面佈局
View view = LayoutInflater.from(context).inflate(R.layout.arc_view_pager_item,null);
text1 = (TextView) view.findViewById(R.id.arc_title);
text2 = (TextView) view.findViewById(R.id.arc_content);
return view;
}
@Override
public void onBind(Context context, int position, MyData data) {
// 資料繫結
// mImageView.setImageResource(data);
text1.setText(data.title);
text2.setText(data.content);
}
}
public List<MyData> mockData(){
List<MyData> datas = new ArrayList<>();
for(int i=0;i<4;i++){
MyData myData = new MyData();
myData.title = "公眾號: Android技術雜貨鋪";
myData.content = "Find your\n"+"Plan"+i;
datas.add(myData);
}
return datas;
}
public static class MyData{
public String title;
public String content;
}
}複製程式碼
最終效果如下:
最後
以上就是關於本期總結的全部內容,Demo程式碼已經上傳Github:github.com/pinguo-zhou…
,歡迎討論。
更多Android乾貨文章,關注公眾號【Android技術雜貨鋪】