android 自定义ViewGroup之浪漫求婚


*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布


1、最终效果

这里写图片描述

有木有发现还是很小清新的感觉这里写图片描述 这里写图片描述

2、看整体效果这是一个scrollView,滑动时每个子view都有一个或多个动画效果,但是如果我们直接给每个子view加上动画去实现这个需求就太low了,而且也不利于扩展,所以这里将会设计一套框架,使别人能很方便的使用我们定义的控件。

3、首先看看我们是怎么使用自己设计的这个控件的

.lly.com.scrollviewgroup.lib.DiscrollViewxmlns:android="http://schemas.android.com/apk/res/android"xmlns:discrollve="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent">.lly.com.scrollviewgroup.lib.DiscrollViewContentandroid:layout_width="match_parent"android:layout_height="match_parent">..."300dp"android:layout_height="180dp"android:layout_gravity="center"discrollve:discrollve_alpha="true"discrollve:discrollve_translation="fromLeft|fromBottom"android:src="@drawable/cheese1" />....lly.com.scrollviewgroup.lib.DiscrollViewContent>
.lly.com.scrollviewgroup.lib.DiscrollView>

discrollve:discrollve_alpha="true"
discrollve:discrollve_translation="fromLeft|fromBottom"

这里我们给系统控件加上自定义属性,这样当别人用我们的控件,简直不要太爽。

不过大家有没有发现这是系统控件哎,你就这么随随便便的给它加个属性,它认识么 不报错你就谢天谢地了 还让它工作,想的美。

带着这个疑惑,我们先来看看系统的ViewGroup.java类是怎么做的。

一般我们在代码中给布局动态添加子控件的时候都会用到addView这个方法
这里我们就跟踪这个方法,最后发现他们会调用到ViewGroup的addview方法

public void addView(View child, int index) {LayoutParams params = child.getLayoutParams();if (params == null) {params = generateDefaultLayoutParams();if (params == null) {throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");}}addView(child, index, params);}

有没有发现这里这里最后的params是怎么来的 不就是子控件的params么。
而addView(child, index, params); 最后会调用addViewInner
下面我们看下addViewInner是怎么做的

private void addViewInner(View child, int index, LayoutParams params,boolean preventRequestLayout) {...if (!checkLayoutParams(params)) {params = generateLayoutParams(params);}if (preventRequestLayout) {child.mLayoutParams = params;} else {child.setLayoutParams(params);}...addInArray(child, index);// tell our childrenif (preventRequestLayout) {child.assignParent(this);} else {child.mParent = this;}...onViewAdded(child);...}

代码还是比较多的,只关注对我们有用的片段
首先它会调用checkLayoutParams(params)

 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {return  p != null;}

如果不等于空就会调用就调用generateLayoutParams

protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {return p;}

继续执行

        if (preventRequestLayout) {child.mLayoutParams = params;} else {child.setLayoutParams(params);}

看到上面的checkLayoutParams和generateLayoutParams方法都比较简单而且是protected的 所以应该是给子类实现的,我们看一个viewgroup的子类 LinearLayout是怎么做的

@Overrideprotected boolean checkLayoutParams(ViewGroup.LayoutParams p) {return p instanceof LinearLayout.LayoutParams;}@Overridepublic LayoutParams generateLayoutParams(AttributeSet attrs) {return new LinearLayout.LayoutParams(getContext(), attrs);}

看到这里就在想我们是不是也可以这么做呢,那当然是可以的 系统都可以了还有什么问题,
接下来我们的大波代码来袭了

public class DiscrollViewContent extends LinearLayout {public DiscrollViewContent(Context context) {super(context);setOrientation(VERTICAL);}public DiscrollViewContent(Context context, AttributeSet attrs) {super(context, attrs);setOrientation(VERTICAL);}public DiscrollViewContent(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);setOrientation(VERTICAL);}/*** 重写addView* @param child* @param index* @param params*/@Overridepublic void addView(View child, int index, ViewGroup.LayoutParams params) {super.addView(asDiscrollvable(child,(MyLayoutParams)params), index, params);}private View asDiscrollvable(View child, MyLayoutParams params) {if(!isDiscrollvable(params)){return child;}DiscrollvableView discrollvableChild = new DiscrollvableView(getContext());discrollvableChild.setDiscrollveAlpha(params.mDiscrollveAlpha);discrollvableChild.setDiscrollveTranslation(params.mDiscrollveTranslation);discrollvableChild.setDiscrollveScaleX(params.mDiscrollveScaleX);discrollvableChild.setDiscrollveScaleY(params.mDiscrollveScaleY);discrollvableChild.setDiscrollveThreshold(params.mDiscrollveThreshold);discrollvableChild.setDiscrollveFromBgColor(params.mDiscrollveFromBgColor);discrollvableChild.setDiscrollveToBgColor(params.mDiscrollveToBgColor);discrollvableChild.addView(child);return discrollvableChild;}/*** 判断是否是我们定义的LayoutParams* @param lp* @return*/private boolean isDiscrollvable(MyLayoutParams lp) {return lp.mDiscrollveAlpha ||lp.mDiscrollveTranslation != -1 ||lp.mDiscrollveScaleX ||lp.mDiscrollveScaleY ||(lp.mDiscrollveFromBgColor != -1 && lp.mDiscrollveToBgColor != -1);}/*** 重写checkLayoutParams* @param p* @return*/@Overrideprotected boolean checkLayoutParams(ViewGroup.LayoutParams p) {return p instanceof MyLayoutParams;}/*** 重写generateDefaultLayoutParams* @return*/@Overrideprotected LinearLayout.LayoutParams generateDefaultLayoutParams() {return new MyLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);}/*** 重写generateLayoutParams* @param attrs* @return*/@Overridepublic LinearLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {return new MyLayoutParams(getContext(), attrs);}/*** 重写generateLayoutParams* @param p* @return*/@Overrideprotected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {return new MyLayoutParams(p.width, p.height);}/*** 自定义LinearLayout.LayoutParams*/class MyLayoutParams extends LinearLayout.LayoutParams {private int mDiscrollveFromBgColor;private int mDiscrollveToBgColor;private float mDiscrollveThreshold;public boolean mDiscrollveAlpha;public boolean mDiscrollveScaleX;public boolean mDiscrollveScaleY;private int mDiscrollveTranslation;public MyLayoutParams(Context context, AttributeSet attrs) {super(context, attrs);TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DiscrollView_LayoutParams);try {mDiscrollveAlpha = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_alpha, false);mDiscrollveScaleX = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleX, false);mDiscrollveScaleY = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleY, false);mDiscrollveTranslation = a.getInt(R.styleable.DiscrollView_LayoutParams_discrollve_translation, -1);mDiscrollveThreshold = a.getFloat(R.styleable.DiscrollView_LayoutParams_discrollve_threshold, 0.0f);mDiscrollveFromBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_fromBgColor, -1);mDiscrollveToBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_toBgColor, -1);} finally {a.recycle();}}public MyLayoutParams(int width, int height) {super(width, height);}}
}

上面这大段代码主要就做了我们上面分析的系统空间

首先继承LinearLayout,
重写了addView,generateLayoutParams,checkLayoutParams
并自定义了一个MyLayoutParams继承自LinearLayout.LayoutParams
在addview的时候我们首先对child进行下处理,判断子view中是否有我们定义属性,没有的话,就用它自己,有的话,我们在外层包一个FrameLayout,让他执行动画,他的子view也将跟着执行。

好了 框架的设计部分完成了,

下面就是动画的实现了

首先看我们的scrollView是怎么做的

public class DiscrollView extends ScrollView {private DiscrollViewContent mContent;public DiscrollView(Context context) {super(context);}public DiscrollView(Context context, AttributeSet attrs) {super(context, attrs);}public DiscrollView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);setupFirstView();}@Overrideprotected void onFinishInflate() {super.onFinishInflate();if(getChildCount() != 1) {throw new IllegalStateException("Discrollview must host one child.");}View content = getChildAt(0);if(!(content instanceof DiscrollViewContent)) {throw new IllegalStateException("Discrollview must host a DiscrollViewContent.");}mContent = (DiscrollViewContent) content;if(mContent.getChildCount() < 2) {throw new IllegalStateException("Discrollview must have at least 2 children.");}}private void setupFirstView() {View first = mContent.getChildAt(0);if(first!=null){first.getLayoutParams().height = getHeight();}}@Overrideprotected void onScrollChanged(int l, int t, int oldl, int oldt) {super.onScrollChanged(l, t, oldl, oldt);onScrollChanged(t);}private void onScrollChanged(int top) {int scrollViewHeight = getHeight();int scrollViewBottom = getAbsoluteBottom();int scrollViewHalfHeight = scrollViewHeight / 2;for(int index = 1;indexif(!(child instanceof DiscrollVable)){continue;}DiscrollVable discrollvable = (DiscrollVable) child;int discrollvableTop = child.getTop();int discrollvableHeight = child.getHeight();int discrollvableAbsoluteTop = discrollvableTop - top;//这个view的下半部分if(scrollViewBottom - child.getBottom() < discrollvableHeight+scrollViewHalfHeight){//子view显示的时候执行if(discrollvableAbsoluteTop <= scrollViewHeight){int visibleGap = scrollViewHeight - discrollvableAbsoluteTop;discrollvable.onDiscrollve(clamp(visibleGap / (float)discrollvableHeight,0.0f,1.0f));}else {//子view还没显示的时候discrollvable.onResetDiscrollve();}}else{if(discrollvableAbsoluteTop <= scrollViewHalfHeight){int visibleGap = scrollViewHalfHeight - discrollvableAbsoluteTop;discrollvable.onDiscrollve(clamp(visibleGap / (float)discrollvableHeight,0.0f,1.0f));}else{discrollvable.onResetDiscrollve();}}}}private float clamp(float value, float max, float min) {return Math.max(Math.min(value,min),max);}public int getAbsoluteBottom() {View last = getChildAt(getChildCount()-1);if(last == null){return 0;}return last.getBottom();}
}

主要就是在滑动的时候 把滑动的百分比传给接口 ,具体由接口的实现类来执行
而实现接口的类就是我们上面的那个FrameLayout。

 @Overridepublic void onDiscrollve(float ratio) {if(ratio >= mDiscrollveThreshold) {ratio = withThreshold(ratio);float ratioInverse = 1 - ratio;if(mDiscrollveAlpha) {setAlpha(ratio);}if(isDiscrollveTranslationFrom(TRANSLATION_FROM_BOTTOM)) {setTranslationY(mHeight * ratioInverse);}if(isDiscrollveTranslationFrom(TRANSLATION_FROM_TOP)) {setTranslationY(-mHeight * ratioInverse);}if(isDiscrollveTranslationFrom(TRANSLATION_FROM_LEFT)) {setTranslationX(-mWidth * ratioInverse);}if(isDiscrollveTranslationFrom(TRANSLATION_FROM_RIGHT)) {setTranslationX(mWidth * ratioInverse);}if(mDiscrollveScaleX) {setScaleX(ratio);}if(mDiscrollveScaleY) {setScaleY(ratio);}if(mDiscrollveFromBgColor != -1 && mDiscrollveToBgColor != -1) {setBackgroundColor((Integer) sArgbEvaluator.evaluate(ratio, mDiscrollveFromBgColor, mDiscrollveToBgColor));}}}@Overridepublic void onResetDiscrollve() {if(mDiscrollveAlpha) {setAlpha(0.0f);}if(isDiscrollveTranslationFrom(TRANSLATION_FROM_BOTTOM)) {setTranslationY(mHeight);}if(isDiscrollveTranslationFrom(TRANSLATION_FROM_TOP)) {setTranslationY(-mHeight);}if(isDiscrollveTranslationFrom(TRANSLATION_FROM_LEFT)) {setTranslationX(-mWidth);}if(isDiscrollveTranslationFrom(TRANSLATION_FROM_RIGHT)) {setTranslationX(mWidth);}if(mDiscrollveScaleX) {setScaleX(0.0f);}if(mDiscrollveScaleY) {setScaleY(0.0f);}if(mDiscrollveFromBgColor != -1 && mDiscrollveToBgColor != -1) {setBackgroundColor(mDiscrollveFromBgColor);}}

代码贴的太多了 底部将给出源码
可以看出 每个类都不是很大,当用户要用的时候只要 在xml中引用我们的控件,就可以实现这个效果,而且他要别的效果的话同样只要在xml中配置就好。

github


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部