├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── jhf
│ │ └── viewgroup
│ │ └── demo
│ │ ├── ArticleCommentLayout.java
│ │ ├── ArticleDetailWebView.java
│ │ ├── ArticleDetailsLayout.java
│ │ ├── CommentListView.java
│ │ └── MainActivity.java
│ └── res
│ ├── layout
│ ├── activity_main.xml
│ ├── adapter_article.xml
│ ├── fragment_article_comment.xml
│ └── fragment_details_article.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradlew
├── gradlew.bat
├── groupviewrolling
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── groupviewrolling
│ │ ├── ArticleDetailsViewGroup.java
│ │ ├── IBaseArticleLayout.java
│ │ ├── IBaseCommentLayout.java
│ │ ├── IWebViewHeightListener.java
│ │ ├── OnViewChangeListener.java
│ │ └── utils
│ │ └── ScreenUtils.java
│ └── res
│ └── values
│ └── strings.xml
└── settings.gradle
/README.md:
--------------------------------------------------------------------------------
1 | # ViewGroupDemo
2 | 新闻客户端详情页,上面WebView新闻内容,下面ListView评论,可以上下滑动,类似今日头条,搜狐新闻等
3 |
4 | #####介绍
5 | 上面展示文章内容用WebView下面展示这个文章的评论用ListView,可以上下滑动来切换文章内容和评论,程序已经发布,经过测试拿来即用
6 |
7 | #####扩展
8 | 只要实现了IBaseArticleLayout和IBaseCommentLayout接口将这两个View放到ArticleDetailsViewGroup容器中可以自定义其他的上下切换的控件
9 |
10 | #####使用方法
11 | MainActivity.java
12 | public class MainActivity extends AppCompatActivity {
13 |
14 | @Override
15 | protected void onCreate(Bundle savedInstanceState) {
16 | super.onCreate(savedInstanceState);
17 | setContentView(R.layout.activity_main);
18 | //一下两个Layout分别实现了IBaseArticleLayout和IBaseCommentLayout接口
19 | ArticleDetailsLayout articleDetailsLayout = new ArticleDetailsLayout(this);
20 | ArticleCommentLayout articleCommentLayout = new ArticleCommentLayout(this);
21 |
22 | ArticleDetailsViewGroup articleDetailsViewGroup = (ArticleDetailsViewGroup)findViewById(R.id.articleDetailsViewGroup);
23 |
24 | articleDetailsViewGroup.addArticleView(articleDetailsLayout);
25 | articleDetailsViewGroup.addCommentView(articleCommentLayout);
26 | articleDetailsViewGroup.requestLayout();
27 | }
28 | }
29 |
30 | activity_main.xml
31 |
37 |
38 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.2"
6 |
7 | defaultConfig {
8 | applicationId "com.jhf.viewgroup.demo"
9 | minSdkVersion 11
10 | targetSdkVersion 23
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | testCompile 'junit:junit:4.12'
25 | compile 'com.android.support:appcompat-v7:23.1.1'
26 | compile project(':groupviewrolling')
27 | }
28 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/jiahongfei/Documents/DeveloperSoftware/adt-bundle-mac-x86_64-20140702/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jhf/viewgroup/demo/ArticleCommentLayout.java:
--------------------------------------------------------------------------------
1 | package com.jhf.viewgroup.demo;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.AdapterView;
8 | import android.widget.BaseAdapter;
9 | import android.widget.LinearLayout;
10 | import android.widget.TextView;
11 | import android.widget.Toast;
12 |
13 | import com.groupviewrolling.ArticleDetailsViewGroup;
14 | import com.groupviewrolling.IBaseCommentLayout;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | /**
20 | *
21 | * @Description: 评论列表
22 | * @version V1.0.0
23 | */
24 | public class ArticleCommentLayout extends LinearLayout implements
25 | View.OnClickListener, IBaseCommentLayout {
26 |
27 | private Context mContext;
28 |
29 | protected CommentListView mPullToRefreshListView;
30 |
31 | private ArticleAdapter mArticleAdapter;
32 |
33 | private List mArticleComments = new ArrayList();
34 |
35 | private ArticleDetailsViewGroup articleDetailsViewGroup;
36 |
37 | public ArticleCommentLayout(Context context, ArticleDetailsViewGroup articleDetailsViewGroup) {
38 | super(context);
39 | mContext = context;
40 | this.articleDetailsViewGroup = articleDetailsViewGroup;
41 | afterViews();
42 | }
43 |
44 | public void afterViews() {
45 | LayoutInflater.from(mContext).inflate(
46 | R.layout.fragment_article_comment, this, true);
47 |
48 | for (int i = 0; i < 60; i++){
49 | mArticleComments.add(i + "");
50 | }
51 |
52 | mPullToRefreshListView = (CommentListView) findViewById(R.id.articleListView);
53 | mPullToRefreshListView.setOverScrollMode(View.OVER_SCROLL_NEVER);
54 | mArticleAdapter = new ArticleAdapter();
55 | mPullToRefreshListView.setAdapter(mArticleAdapter);
56 | mPullToRefreshListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
57 | @Override
58 | public void onItemClick(AdapterView> parent, View view, int position, long id) {
59 | if(!articleDetailsViewGroup.isScrollerFinished()){
60 | return ;
61 | }
62 | Toast.makeText(mContext, "position : " + position, Toast.LENGTH_SHORT).show();
63 | }
64 | });
65 | }
66 |
67 | @Override
68 | public boolean isFirstViewTop() {
69 | return mPullToRefreshListView
70 | .isFirstViewTop();
71 | }
72 |
73 | @Override
74 | public void setIsScroll(boolean isScroll) {
75 | mPullToRefreshListView
76 | .setIsScroll(isScroll);
77 | }
78 |
79 | @Override
80 | public void onClick(View v) {
81 |
82 | }
83 |
84 | class ArticleAdapter extends BaseAdapter{
85 |
86 | @Override
87 | public int getCount() {
88 | return mArticleComments.size();
89 | }
90 |
91 | @Override
92 | public Object getItem(int position) {
93 | return mArticleComments.get(position);
94 | }
95 |
96 | @Override
97 | public long getItemId(int position) {
98 | return position;
99 | }
100 |
101 | @Override
102 | public View getView(int position, View convertView, ViewGroup parent) {
103 |
104 | TextView textView = null;
105 |
106 | if(null == convertView){
107 | convertView = LayoutInflater.from(mContext).inflate(R.layout.adapter_article, null);
108 |
109 | }else {
110 |
111 | }
112 | textView = (TextView) convertView.findViewById(R.id.textView);
113 | textView.setText(mArticleComments.get(position));
114 |
115 | return convertView;
116 | }
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jhf/viewgroup/demo/ArticleDetailWebView.java:
--------------------------------------------------------------------------------
1 | package com.jhf.viewgroup.demo;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.os.Build;
6 | import android.util.AttributeSet;
7 | import android.view.KeyEvent;
8 | import android.view.MotionEvent;
9 | import android.webkit.WebChromeClient;
10 | import android.webkit.WebSettings;
11 | import android.webkit.WebView;
12 |
13 | /**
14 | * @Description: Webview展示文章详情,继承WebView主要是判断Html内容是否滚动到底了
15 | *
16 | * @version V1.0.0
17 | */
18 | public class ArticleDetailWebView extends WebView {
19 |
20 | public interface ScrollInterface {
21 | public void onScrollChanged(int l, int t, int oldl, int oldt);
22 | }
23 |
24 | private static final String BASE_URL = "file:///android_asset/";
25 | private static final String MIME_TYPE = "text/html";
26 | private static final String ENCODING = "utf-8";
27 | private static final String BASIC_HTML = "basic.html";
28 | private static final String BASIC_CASE_DETAILS_HTML = "basic_case_detail.html";
29 |
30 | private Context mContext;
31 | private ScrollInterface mScrollInterface;
32 |
33 | private boolean isScrollBottom = false;
34 | /** true可以滑动,false禁止滑动 */
35 | private boolean isScroll = true;
36 | private float mWebViewContentHeight = 0.0F;
37 |
38 | public ArticleDetailWebView(Context context, int cacheMode) {
39 | super(context);
40 | init(context, cacheMode);
41 | }
42 |
43 | public ArticleDetailWebView(Context context) {
44 | super(context);
45 | init(context, WebSettings.LOAD_NO_CACHE);
46 | }
47 |
48 | public ArticleDetailWebView(Context context, AttributeSet attrs) {
49 | super(context, attrs);
50 | init(context, WebSettings.LOAD_NO_CACHE);
51 | }
52 |
53 | public ArticleDetailWebView(Context context, AttributeSet attrs,
54 | int defStyle) {
55 | super(context, attrs, defStyle);
56 | init(context, WebSettings.LOAD_NO_CACHE);
57 | }
58 |
59 | @SuppressLint("NewApi")
60 | private void init(Context context, int cacheMode) {
61 | mContext = context;
62 |
63 | this.setScrollBarStyle(0);
64 | this.setHorizontalScrollBarEnabled(false);
65 | this.getSettings().setLoadsImagesAutomatically(true);
66 | this.getSettings().setJavaScriptEnabled(true);
67 | this.getSettings().setCacheMode(cacheMode);
68 | this.getSettings().setUseWideViewPort(true);
69 | this.getSettings().setLoadWithOverviewMode(true);
70 | this.setWebChromeClient(new WebChromeClient());
71 | this.setBackgroundColor(0);
72 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
73 | this.setLayerType(WebView.LAYER_TYPE_SOFTWARE, null);
74 |
75 | }
76 |
77 | @Override
78 | protected void onScrollChanged(int l, int t, int oldl, int oldt) {
79 |
80 | super.onScrollChanged(l, t, oldl, oldt);
81 | float webViewContentHeight = this.getContentHeight() * this.getScale();
82 | // float webViewContentHeight = this.getContentHeight() * mNewScale;
83 | this.mWebViewContentHeight = webViewContentHeight;
84 | float webViewCurrentHeight = (this.getHeight() + this.getScrollY());
85 |
86 | if ((webViewContentHeight - webViewCurrentHeight) <= 2.0F) {
87 | isScrollBottom = true;
88 | } else {
89 | isScrollBottom = false;
90 | }
91 |
92 | if (null != mScrollInterface) {
93 | mScrollInterface.onScrollChanged(l, t, oldl, oldt);
94 | }
95 |
96 | }
97 |
98 | /**
99 | * true可以滑动,false禁止滑动
100 | *
101 | * @param isScroll
102 | */
103 | public void setIsScroll(boolean isScroll) {
104 | this.isScroll = isScroll;
105 | }
106 |
107 | @Override
108 | public boolean dispatchKeyEvent(KeyEvent event) {
109 | if (!isScroll) {
110 | return true;
111 | }
112 | return super.dispatchKeyEvent(event);
113 | }
114 |
115 | @Override
116 | public boolean onTouchEvent(MotionEvent ev) {
117 |
118 | if (!isScroll) {
119 | return true;
120 | }
121 | return super.onTouchEvent(ev);
122 | }
123 |
124 | public void setScrollBottom(boolean isScrollBottom) {
125 | this.isScrollBottom = isScrollBottom;
126 | }
127 |
128 | public boolean isScrollBottom() {
129 | return isScrollBottom;
130 | }
131 |
132 | public void setOnScrollChangeListener(ScrollInterface scrollInterface) {
133 | this.mScrollInterface = scrollInterface;
134 | }
135 |
136 |
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jhf/viewgroup/demo/ArticleDetailsLayout.java:
--------------------------------------------------------------------------------
1 | package com.jhf.viewgroup.demo;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 | import android.view.ViewGroup;
6 | import android.webkit.WebView;
7 | import android.webkit.WebViewClient;
8 | import android.widget.LinearLayout;
9 |
10 | import com.groupviewrolling.ArticleDetailsViewGroup;
11 | import com.groupviewrolling.IBaseArticleLayout;
12 |
13 | /**
14 | *
15 | * @Title: ArticleDetailsLayout.java
16 | * @Package hbyc.china.medical.view.modules.home.articleviewpager.fragment
17 | * @ClassName: ArticleDetailsLayout
18 | * @Description: 详情
19 | * @author jiahongfei jiahongfeinew@163.com
20 | * @date 2015-7-13 下午3:23:22
21 | * @version V1.0.0
22 | */
23 | public class ArticleDetailsLayout extends LinearLayout implements IBaseArticleLayout {
24 |
25 | private Context mContext;
26 | private ArticleDetailWebView mArticleDetailWebView;
27 | private ViewGroup mArticleDetailsLayout;
28 |
29 | public ArticleDetailsLayout(Context context, ArticleDetailsViewGroup articleDetailsViewGroup) {
30 | super(context);
31 | mContext = context;
32 | LayoutInflater.from(mContext).inflate(
33 | R.layout.fragment_details_article, this, true);
34 |
35 | mArticleDetailsLayout = (ViewGroup)findViewById(R.id.articleDetailsLayout);
36 |
37 | mArticleDetailWebView = new ArticleDetailWebView(mContext);
38 | mArticleDetailWebView.loadUrl("http://www.baidu.com/");
39 | mArticleDetailWebView.setWebViewClient(new WebViewClient(){
40 | @Override
41 | public boolean shouldOverrideUrlLoading(WebView view, String url) {
42 | mArticleDetailWebView.loadUrl(url);
43 | return true;
44 | }
45 | });
46 | LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
47 | LayoutParams.MATCH_PARENT);
48 | mArticleDetailWebView.setLayoutParams(layoutParams);
49 | mArticleDetailsLayout.removeAllViews();
50 | mArticleDetailsLayout.addView(mArticleDetailWebView);
51 | }
52 |
53 | @Override
54 | public void setIsScroll(boolean isScroll) {
55 | mArticleDetailWebView.setIsScroll(isScroll);
56 | }
57 |
58 | @Override
59 | public boolean isScrollBottom() {
60 | return mArticleDetailWebView.isScrollBottom();
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jhf/viewgroup/demo/CommentListView.java:
--------------------------------------------------------------------------------
1 | package com.jhf.viewgroup.demo;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.util.AttributeSet;
6 | import android.view.MotionEvent;
7 | import android.view.View;
8 | import android.widget.ListView;
9 |
10 | /**
11 | * 评论ListView,继承ListView主要是拿到是否滚动到顶
12 | * Created by jiahongfei on 15/12/1.
13 | */
14 | public class CommentListView extends ListView{
15 |
16 | /** true listview在最顶端,false不是在最顶端 */
17 | private boolean isFirstViewTop = false;
18 | /**true可以滑动,false不能滑动*/
19 | private boolean isScroll = true;
20 |
21 | public CommentListView(Context context) {
22 | super(context);
23 | }
24 |
25 | public CommentListView(Context context, AttributeSet attrs) {
26 | super(context, attrs);
27 | }
28 |
29 | public CommentListView(Context context, AttributeSet attrs, int defStyleAttr) {
30 | super(context, attrs, defStyleAttr);
31 | }
32 |
33 |
34 | @Override
35 | protected void dispatchDraw(Canvas canvas) {
36 | /**
37 | * This is a bit hacky, but Samsung's ListView has got a bug in it
38 | * when using Header/Footer Views and the list is empty. This masks
39 | * the issue so that it doesn't cause an FC. See Issue #66.
40 | */
41 | try {
42 | super.dispatchDraw(canvas);
43 |
44 | if (0 == this.getFirstVisiblePosition()) {
45 | View firstView = this.getChildAt(0);
46 | if (null != firstView) {
47 | int firstTop = firstView.getTop();
48 | // System.out.println("firstTop : " + firstTop);
49 | if (Math.abs(firstTop) <= 5) {
50 | isFirstViewTop = true;
51 | } else {
52 | isFirstViewTop = false;
53 | }
54 | } else {
55 | if (getAdapter().getCount() <= 0) {
56 | isFirstViewTop = true;
57 | } else {
58 | isFirstViewTop = false;
59 | }
60 | }
61 | } else {
62 |
63 | if (getAdapter().getCount() <= 0) {
64 | isFirstViewTop = true;
65 | } else {
66 | isFirstViewTop = false;
67 | }
68 | }
69 | getParent().requestDisallowInterceptTouchEvent(
70 | !isFirstViewTop);
71 | } catch (IndexOutOfBoundsException e) {
72 | e.printStackTrace();
73 | } catch (Exception e) {
74 | e.printStackTrace();
75 | }
76 | }
77 |
78 | @Override
79 | public boolean dispatchTouchEvent(MotionEvent ev) {
80 |
81 | if (!isScroll) {
82 | return true;
83 | }
84 | return super.dispatchTouchEvent(ev);
85 | }
86 |
87 | @Override
88 | public boolean onInterceptTouchEvent(MotionEvent ev) {
89 | if (!isScroll) {
90 | return true;
91 | }
92 | return super.onInterceptTouchEvent(ev);
93 | }
94 |
95 | @Override
96 | public boolean onTouchEvent(MotionEvent ev) {
97 |
98 | if (!isScroll) {
99 | return true;
100 | }
101 |
102 | return super.onTouchEvent(ev);
103 | }
104 | /**
105 | * true可以滑动,false不能滑动
106 | * @param isScroll
107 | */
108 | public void setIsScroll(boolean isScroll) {
109 | this.isScroll = isScroll;
110 | }
111 |
112 | public boolean isFirstViewTop() {
113 | return isFirstViewTop;
114 | }
115 |
116 |
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jhf/viewgroup/demo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.jhf.viewgroup.demo;
2 |
3 | import android.support.v7.app.AppCompatActivity;
4 | import android.os.Bundle;
5 |
6 | import com.groupviewrolling.ArticleDetailsViewGroup;
7 |
8 | public class MainActivity extends AppCompatActivity {
9 |
10 | @Override
11 | protected void onCreate(Bundle savedInstanceState) {
12 | super.onCreate(savedInstanceState);
13 | setContentView(R.layout.activity_main);
14 |
15 | ArticleDetailsViewGroup articleDetailsViewGroup = (ArticleDetailsViewGroup)findViewById(R.id.articleDetailsViewGroup);
16 |
17 | ArticleDetailsLayout articleDetailsLayout = new ArticleDetailsLayout(this,articleDetailsViewGroup);
18 | ArticleCommentLayout articleCommentLayout = new ArticleCommentLayout(this,articleDetailsViewGroup);
19 |
20 | articleDetailsViewGroup.addArticleView(articleDetailsLayout);
21 | articleDetailsViewGroup.addCommentView(articleCommentLayout);
22 | articleDetailsViewGroup.requestLayout();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/adapter_article.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_article_comment.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_details_article.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jiahongfei/ViewGroupDemo/517823032f24be6e45c0e660a5ee1f41537a88fc/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jiahongfei/ViewGroupDemo/517823032f24be6e45c0e660a5ee1f41537a88fc/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jiahongfei/ViewGroupDemo/517823032f24be6e45c0e660a5ee1f41537a88fc/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jiahongfei/ViewGroupDemo/517823032f24be6e45c0e660a5ee1f41537a88fc/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jiahongfei/ViewGroupDemo/517823032f24be6e45c0e660a5ee1f41537a88fc/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ViewGroupDemo
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:1.3.0'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/groupviewrolling/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/groupviewrolling/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.2"
6 |
7 | defaultConfig {
8 | minSdkVersion 11
9 | targetSdkVersion 23
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | compile fileTree(dir: 'libs', include: ['*.jar'])
23 | testCompile 'junit:junit:4.12'
24 | compile 'com.android.support:appcompat-v7:23.1.1'
25 | }
26 |
--------------------------------------------------------------------------------
/groupviewrolling/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/jiahongfei/Documents/DeveloperSoftware/adt-bundle-mac-x86_64-20140702/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/groupviewrolling/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/groupviewrolling/src/main/java/com/groupviewrolling/ArticleDetailsViewGroup.java:
--------------------------------------------------------------------------------
1 | package com.groupviewrolling;
2 |
3 |
4 | import android.content.Context;
5 | import android.util.AttributeSet;
6 | import android.view.MotionEvent;
7 | import android.view.VelocityTracker;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.Scroller;
11 |
12 | import com.groupviewrolling.utils.ScreenUtils;
13 |
14 | /**
15 | *
16 | * @Title: ArticleDetailsViewGroup.java
17 | * @ClassName: ArticleDetailsViewGroup
18 | * @Description: 文章详情ViewGroup,用来切换详情和评论
19 | * @author jiahongfei jiahongfeinew@163.com
20 | * @date 2015-3-4 上午10:25:08
21 | * @version V1.0.0
22 | */
23 | public class ArticleDetailsViewGroup extends ViewGroup {
24 |
25 | private VelocityTracker mVelocityTracker; // 用于判断甩动手势
26 | private static final int SNAP_VELOCITY = 600; // X轴速度基值,大于该值时进行切换
27 | private Scroller mScroller; // 滑动控制
28 | private int mCurScreen; // 当前页面为第几屏
29 | private int mDefaultScreen = 0;
30 | private float mLastMotionY;
31 | private float mLastMotionDispatchY = 0;
32 | private int mTouchSlop = 0;
33 | private int mWebViewHeight = -1;
34 |
35 | private boolean isScrollBy = false;
36 |
37 | private Context mContext;
38 |
39 | private IBaseArticleLayout mIBaseArticleLayout;
40 | private IBaseCommentLayout mIBaseCommentLayout;
41 |
42 | private OnViewChangeListener mOnViewChangeListener;
43 |
44 | public ArticleDetailsViewGroup(Context context) {
45 | super(context);
46 | init(context);
47 | }
48 |
49 | public ArticleDetailsViewGroup(Context context, AttributeSet attrs) {
50 | super(context, attrs);
51 | init(context);
52 | }
53 |
54 | public ArticleDetailsViewGroup(Context context, AttributeSet attrs,
55 | int defStyle) {
56 | super(context, attrs, defStyle);
57 | init(context);
58 | }
59 |
60 | private void init(Context context) {
61 | mContext = context;
62 | mCurScreen = mDefaultScreen;
63 | mWebViewHeight = -1;
64 | mScroller = new Scroller(context);
65 |
66 | mTouchSlop = ScreenUtils.getTouchSlop(mContext);
67 | System.out.println("touchSlop : " + mTouchSlop);
68 | mTouchSlop = 1;
69 |
70 | }
71 |
72 | public void setWebViewHeight(int webViewHeight) {
73 | this.mWebViewHeight = webViewHeight;
74 | }
75 |
76 | /**
77 | * 设置文章
78 | *
79 | * @param articleView
80 | */
81 | public void addArticleView(View articleView) {
82 | if (articleView instanceof IBaseArticleLayout) {
83 | mIBaseArticleLayout = (IBaseArticleLayout) articleView;
84 | addView(articleView, 0);
85 | } else {
86 | // articleView没有实现IBaseArticleLayout接口
87 | throw new RuntimeException(
88 | "articleView No interface IBaseArticleLayout");
89 | }
90 |
91 | }
92 |
93 | public void addCommentView(View commentView) {
94 | if (commentView instanceof IBaseCommentLayout) {
95 | mIBaseCommentLayout = (IBaseCommentLayout) commentView;
96 | addView(commentView, 1);
97 | } else {
98 | // articleView没有实现IBaseCommentLayout接口
99 | throw new RuntimeException(
100 | "articleView No interface IBaseCommentLayout");
101 | }
102 | }
103 |
104 | @Override
105 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
106 | // if (changed) {
107 | int childTop = 0;
108 | final int childCount = getChildCount();
109 | for (int i = 0; i < childCount; i++) {
110 | final View childView = getChildAt(i);
111 | if (childView.getVisibility() != View.GONE) {
112 | int childHeight = 0;
113 | if (0 == i && -1 != mWebViewHeight) {
114 | childHeight = mWebViewHeight;
115 | } else {
116 | childHeight = childView.getMeasuredHeight();
117 | }
118 | childView.layout(0, childTop, childView.getMeasuredWidth(),
119 | childTop + childHeight);
120 | childTop += childHeight;
121 | }
122 | }
123 | // }
124 | }
125 |
126 | @Override
127 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
128 |
129 | int widthMode = MeasureSpec.getMode(widthMeasureSpec);
130 | int heightMode = MeasureSpec.getMode(heightMeasureSpec);
131 | int widthSize = MeasureSpec.getSize(widthMeasureSpec);
132 | int heightSize = MeasureSpec.getSize(heightMeasureSpec);
133 |
134 | final int count = getChildCount();
135 | for (int i = 0; i < count; i++) {
136 | if (0 == i && -1 != mWebViewHeight) {
137 | getChildAt(i).measure(widthMeasureSpec,
138 | MeasureSpec.EXACTLY + mWebViewHeight);
139 | } else {
140 | getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
141 | }
142 | }
143 |
144 | if(-1 != mWebViewHeight){
145 | scrollTo(0, mCurScreen * mWebViewHeight);
146 | }else {
147 | scrollTo(0, mCurScreen * getHeight());
148 | }
149 |
150 |
151 | setMeasuredDimension(widthSize, heightSize);
152 |
153 | }
154 |
155 | public void snapToDestination() {
156 |
157 | int screenHeight = 0;
158 | if (-1 == mWebViewHeight) {
159 | screenHeight = getHeight();
160 | } else {
161 | screenHeight = mWebViewHeight;
162 | }
163 |
164 | final int destScreen = (getScrollY() + screenHeight / 2) / screenHeight;
165 | snapToScreen(destScreen);
166 | }
167 |
168 | // 使屏幕移动到第whichScreen+1屏
169 | public void snapToScreen(int whichScreen) {
170 |
171 | int webViewHeight = 0;
172 |
173 | if (-1 == mWebViewHeight) {
174 | webViewHeight = getHeight();
175 | } else {
176 | webViewHeight = mWebViewHeight;
177 | }
178 |
179 | // if (getScrollY() != (whichScreen * webViewHeight)) {
180 | final int delta = whichScreen * webViewHeight - getScrollY();
181 | mScroller.startScroll(0, getScrollY(), 0, delta,
182 | (int) (Math.abs(delta) * 0.5));
183 | mCurScreen = whichScreen;
184 | invalidate();
185 |
186 | if (mOnViewChangeListener != null) {
187 | mOnViewChangeListener.OnViewChange(mCurScreen);
188 | }
189 | // }
190 | }
191 |
192 | @Override
193 | public void computeScroll() {
194 | if (mScroller.computeScrollOffset()) {
195 | scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
196 | postInvalidate();
197 | }
198 | super.computeScroll();
199 |
200 | }
201 |
202 | @Override
203 | public boolean dispatchTouchEvent(MotionEvent event) {
204 |
205 | final int action = event.getAction();
206 | final float x = event.getX();
207 | final float y = event.getY();
208 |
209 | switch (action) {
210 | case MotionEvent.ACTION_DOWN:
211 |
212 | if (mVelocityTracker == null) {
213 | mVelocityTracker = VelocityTracker.obtain();
214 | mVelocityTracker.addMovement(event);
215 | }
216 | if (!mScroller.isFinished()) {
217 | mScroller.abortAnimation();
218 | }
219 | mLastMotionY = y;
220 |
221 | break;
222 |
223 | case MotionEvent.ACTION_MOVE:
224 |
225 | int tempDeltaY = (int) (mLastMotionY - y);
226 | if (tempDeltaY < 0 && Math.abs(tempDeltaY) >= mTouchSlop) {
227 | // 向下
228 | // System.out.println("向下");
229 | if (isScrollBy || (mIBaseCommentLayout.isFirstViewTop())) {
230 | if (isScrollBy || 1 == mCurScreen) {
231 | if (0 == mLastMotionDispatchY) {
232 | mLastMotionDispatchY = y;
233 | }
234 | int deltaY = (int) (mLastMotionDispatchY - y);
235 | if (IsCanMove(deltaY)) {
236 | if (mVelocityTracker != null) {
237 | mVelocityTracker.addMovement(event);
238 | }
239 | mLastMotionDispatchY = y;
240 | mIBaseArticleLayout.setIsScroll(false);
241 | mIBaseCommentLayout.setIsScroll(false);
242 | // 正向或者负向移动,屏幕跟随手指移动
243 | if (deltaY + getScrollY() < 0) {
244 | scrollTo(0, 0);
245 | } else {
246 | scrollBy(0, deltaY);
247 | }
248 | // }
249 | } else {
250 | return true;
251 | }
252 | }
253 | }
254 | } else if (tempDeltaY > 0 && Math.abs(tempDeltaY) >= mTouchSlop) {
255 | // 向上
256 | if (isScrollBy || mIBaseArticleLayout.isScrollBottom()) {
257 | if (isScrollBy || 0 == mCurScreen) {
258 | if (0 == mLastMotionDispatchY) {
259 | mLastMotionDispatchY = y;
260 | }
261 | int deltaY = (int) (mLastMotionDispatchY - y);
262 | if (IsCanMove(deltaY)) {
263 | if (mVelocityTracker != null) {
264 | mVelocityTracker.addMovement(event);
265 | }
266 | mLastMotionY = y;
267 | mLastMotionDispatchY = y;
268 | mIBaseArticleLayout.setIsScroll(false);
269 | mIBaseCommentLayout.setIsScroll(false);
270 | // 正向或者负向移动,屏幕跟随手指移动
271 | if (deltaY + getScrollY() > getHeight()) {
272 | scrollTo(0, getHeight());
273 | } else {
274 | scrollBy(0, deltaY);
275 | }
276 | } else {
277 | return true;
278 | }
279 | }
280 | }
281 | } else {
282 | return true;
283 | }
284 |
285 | mLastMotionY = y;
286 |
287 | break;
288 |
289 | case MotionEvent.ACTION_UP:
290 |
291 | isScrollBy = false;
292 | mLastMotionDispatchY = 0;
293 | mIBaseArticleLayout.setIsScroll(true);
294 | mIBaseCommentLayout.setIsScroll(true);
295 |
296 | int velocityY = 0;
297 | if (mIBaseArticleLayout.isScrollBottom()
298 | && mIBaseCommentLayout.isFirstViewTop()
299 | && mVelocityTracker != null) {
300 | mVelocityTracker.addMovement(event);
301 | mVelocityTracker.computeCurrentVelocity(1000);
302 | // 得到Y轴方向手指移动速度
303 | velocityY = (int) mVelocityTracker.getYVelocity();
304 | }
305 | if (velocityY > SNAP_VELOCITY && mCurScreen > 0) {
306 | // Fling enough to move top
307 | snapToScreen(mCurScreen - 1);
308 | } else if (velocityY < -SNAP_VELOCITY
309 | && mCurScreen < getChildCount() - 1) {
310 | // Fling enough to move bottom
311 | snapToScreen(mCurScreen + 1);
312 | } else {
313 | snapToDestination();
314 | }
315 |
316 | if (mVelocityTracker != null) {
317 | mVelocityTracker.recycle();
318 | mVelocityTracker = null;
319 | }
320 | break;
321 | case MotionEvent.ACTION_CANCEL: {
322 | break;
323 | }
324 | }
325 | return super.dispatchTouchEvent(event);
326 |
327 | }
328 |
329 | @Override
330 | public void scrollBy(int x, int y) {
331 | isScrollBy = true;
332 | super.scrollBy(x, y);
333 | }
334 |
335 | private boolean IsCanMove(int deltaY) {
336 |
337 | int webViewHeight = 0;
338 | if (-1 == mWebViewHeight) {
339 | webViewHeight = getHeight();
340 | } else {
341 | webViewHeight = mWebViewHeight;
342 | }
343 |
344 | // deltaY<0说明手指向下划
345 | if (getScrollY() <= 0 && deltaY < 0) {
346 | return false;
347 | }
348 | // deltaX>0说明手指向上划
349 | if (getScrollY() >= (getChildCount() - 1) * webViewHeight && deltaY > 0) {
350 | return false;
351 | }
352 | return true;
353 | }
354 |
355 | public void SetOnViewChangeListener(OnViewChangeListener listener) {
356 | mOnViewChangeListener = listener;
357 | }
358 |
359 | public int getCurrentItem() {
360 | return mCurScreen;
361 | }
362 |
363 | public void setCurrentItem(int item) {
364 | snapToScreen(item);
365 | }
366 |
367 | /**
368 | * 判断Scroller是否完成
369 | *
370 | * @return
371 | */
372 | public boolean isScrollerFinished() {
373 | return mScroller.isFinished();
374 | }
375 |
376 | }
377 |
--------------------------------------------------------------------------------
/groupviewrolling/src/main/java/com/groupviewrolling/IBaseArticleLayout.java:
--------------------------------------------------------------------------------
1 | package com.groupviewrolling;
2 |
3 | /**
4 | *
5 | * @Title: ArticleDetailsLayout.java
6 | * @Package hbyc.china.medical.view.modules.home.article
7 | * @ClassName: BaseArticleLayout
8 | * @Description: 文章详情Layout
9 | * @author jiahongfei jiahongfeinew@163.com
10 | * @date 2015-3-4 上午10:38:47
11 | * @version V1.0.0
12 | */
13 | public interface IBaseArticleLayout {
14 |
15 | public void setIsScroll(boolean isScroll) ;
16 |
17 | public boolean isScrollBottom();
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/groupviewrolling/src/main/java/com/groupviewrolling/IBaseCommentLayout.java:
--------------------------------------------------------------------------------
1 | package com.groupviewrolling;
2 |
3 |
4 | /**
5 | *
6 | * @Title: BaseCommentLayout.java
7 | * @Package hbyc.china.medical.view.modules.home.article
8 | * @ClassName: BaseCommentLayout
9 | * @Description: 评论Layout
10 | * @author jiahongfei jiahongfeinew@163.com
11 | * @date 2015-3-4 上午10:42:00
12 | * @version V1.0.0
13 | */
14 | public interface IBaseCommentLayout {
15 |
16 | public boolean isFirstViewTop();
17 |
18 | public void setIsScroll(boolean isScroll);
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/groupviewrolling/src/main/java/com/groupviewrolling/IWebViewHeightListener.java:
--------------------------------------------------------------------------------
1 | package com.groupviewrolling;
2 | /**
3 | *
4 | * @Title: IWebViewHeightListener.java
5 | * @Package hbyc.china.medical.view.modules.home.articlegroupview
6 | * @ClassName: IWebViewHeightListener
7 | * @Description: 加载完url调用js获取WebView内容的高度,成功返回
8 | * @author jiahongfei jiahongfeinew@163.com
9 | * @date 2015-7-14 下午3:04:32
10 | * @version V1.0.0
11 | */
12 | public interface IWebViewHeightListener {
13 |
14 | public void onWebViewHeight(int height);
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/groupviewrolling/src/main/java/com/groupviewrolling/OnViewChangeListener.java:
--------------------------------------------------------------------------------
1 | package com.groupviewrolling;
2 |
3 | public interface OnViewChangeListener {
4 | public void OnViewChange(int view);
5 | }
6 |
--------------------------------------------------------------------------------
/groupviewrolling/src/main/java/com/groupviewrolling/utils/ScreenUtils.java:
--------------------------------------------------------------------------------
1 | package com.groupviewrolling.utils;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.graphics.Rect;
6 | import android.util.DisplayMetrics;
7 | import android.view.View;
8 | import android.view.ViewConfiguration;
9 |
10 | /**
11 | *
12 | * @Title: ScreenUtils.java
13 | * @Package com.android.support.framework.utils
14 | * @ClassName: ScreenUtils
15 | * @Description: 和屏幕相关的(屏幕宽高等)
16 | * @author jiahongfei jiahongfeinew@163.com
17 | * @date Nov 5, 2014 2:22:11 PM
18 | * @version V1.0.0
19 | */
20 | public class ScreenUtils {
21 |
22 | /**
23 | * 获取屏幕宽高
24 | *
25 | * @param context
26 | * @return 数组第一个元素宽,第二个元素高
27 | */
28 | public static int[] getScreenBounds(Context context) {
29 |
30 | int[] screenBounds = new int[2];
31 |
32 | DisplayMetrics displayMetrics = context.getResources()
33 | .getDisplayMetrics();
34 | screenBounds[0] = displayMetrics.widthPixels;
35 | screenBounds[1] = displayMetrics.heightPixels;
36 | return screenBounds;
37 | }
38 |
39 | /**
40 | * 根据dip返回当前设备上的px值
41 | *
42 | * @param context
43 | * @param dip
44 | * @return
45 | */
46 | public static int dipToPx(Context context, int dip) {
47 | int px = 0;
48 | DisplayMetrics dm = new DisplayMetrics();
49 | dm = context.getApplicationContext().getResources().getDisplayMetrics();
50 | float density = dm.density;
51 | px = (int) (dip * density);
52 | return px;
53 | }
54 |
55 | public static int getTouchSlop(Context context) {
56 | final ViewConfiguration configuration = ViewConfiguration.get(context);
57 | return configuration.getScaledTouchSlop();
58 | }
59 |
60 | public static int getStatusBarHeight(Activity activity) {
61 | Rect frame = new Rect();
62 | activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
63 | int statusBarHeight = frame.top;
64 | return statusBarHeight;
65 | }
66 |
67 | /**
68 | * 这个方法是将view的左上角坐标存入数组中.此坐标是相对当前activity而言.
69 | * 若是普通activity,则y坐标为可见的状态栏高度+可见的标题栏高度+view左上角到标题栏底部的距离.
70 | * 可见的意思是:在隐藏了状态栏/标题栏的情况下,它们的高度以0计算.
71 | * 若是对话框式的activity,则y坐标为可见的标题栏高度+view到标题栏底部的距离. 此时是无视状态栏的有无的.
72 | *
73 | * @param view
74 | * @return
75 | */
76 | public static int[] getLocationInWindow(View view) {
77 | int[] position = new int[2];
78 | view.getLocationInWindow(position);
79 | System.out.println("getLocationInWindow:" + position[0] + ","
80 | + position[1]);
81 | return position;
82 | }
83 |
84 | /**
85 | * 这个方法跟上面的差不多,也是将view的左上角坐标存入数组中.但此坐标是相对整个屏幕而言. y坐标为view左上角到屏幕顶部的距离.
86 | *
87 | * @param view
88 | * @return
89 | */
90 | public static int[] getLocationOnScreen(View view) {
91 | int[] position = new int[2];
92 | view.getLocationOnScreen(position);
93 | System.out.println("getLocationOnScreen:" + position[0] + ","
94 | + position[1]);
95 | return position;
96 | }
97 |
98 | /**
99 | * 这个方法是构建一个Rect用来"套"这个view.此Rect的坐标是相对当前activity而言.
100 | * 若是普通activity,则Rect的top为可见的状态栏高度+可见的标题栏高度+Rect左上角到标题栏底部的距离.
101 | * 若是对话框式的activity,则y坐标为Rect的top为可见的标题栏高度+Rect左上角到标题栏底部的距离. 此时是无视状态栏的有无的.
102 | *
103 | * @param view
104 | * @return
105 | */
106 | public static Rect getGlobalVisibleRect(View view) {
107 | Rect viewRect = new Rect();
108 | view.getGlobalVisibleRect(viewRect);
109 | System.out.println(viewRect);
110 | return viewRect;
111 | }
112 |
113 | /**
114 | * 这个方法获得的Rect的top和left都是0,也就是说,仅仅能通过这个Rect得到View的宽度和高度....
115 | *
116 | * @param view
117 | * @return
118 | */
119 | public static Rect getLocalVisibleRect(View view) {
120 | Rect globeRect = new Rect();
121 | view.getLocalVisibleRect(globeRect);
122 | return globeRect;
123 | }
124 |
125 | }
126 |
--------------------------------------------------------------------------------
/groupviewrolling/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | groupviewrolling
3 |
4 |
5 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':groupviewrolling'
2 |
--------------------------------------------------------------------------------