57 | * Returns the number of types of Views that will be created by
58 | * {@link #getView}. Each type represents a set of views that can be
59 | * converted in {@link #getView}. If the adapter always returns the same
60 | * type of View for all items, this method should return 1.
61 | *
62 | *
63 | * This method will only be called when when the adapter is set on the
64 | * the {@link AdapterView}.
65 | *
66 | *
67 | * @return The number of types of Views that will be created by this adapter
68 | */
69 | public int getViewTypeCount() {
70 | return 1;
71 | }
72 |
73 | /**
74 | * Get the type of View that will be created by {@link #getView} for the specified item.
75 | *
76 | * @param position The position of the item within the adapter's data set whose view type we
77 | * want.
78 | * @return An integer representing the type of View. Two views should share the same type if one
79 | * can be converted to the other in {@link #getView}. Note: Integers must be in the
80 | * range 0 to {@link #getViewTypeCount} - 1. {@link #IGNORE_ITEM_VIEW_TYPE} can
81 | * also be returned.
82 | * @see #IGNORE_ITEM_VIEW_TYPE
83 | */
84 | @SuppressWarnings("UnusedParameters") // Argument potentially used by subclasses.
85 | public int getItemViewType(int position) {
86 | return 0;
87 | }
88 |
89 | /**
90 | * Get a View that displays the data at the specified position in the data set. You can either
91 | * create a View manually or inflate it from an XML layout file. When the View is inflated, the
92 | * parent View (GridView, ListView...) will apply default layout parameters unless you use
93 | * {@link android.view.LayoutInflater#inflate(int, android.view.ViewGroup, boolean)}
94 | * to specify a root view and to prevent attachment to the root.
95 | *
96 | * @param position The position of the item within the adapter's data set of the item whose view
97 | * we want.
98 | * @param convertView The old view to reuse, if possible. Note: You should check that this view
99 | * is non-null and of an appropriate type before using. If it is not possible to convert
100 | * this view to display the correct data, this method can create a new view.
101 | * Heterogeneous lists can specify their number of view types, so that this View is
102 | * always of the right type (see {@link #getViewTypeCount()} and
103 | * {@link #getItemViewType(int)}).
104 | * @param parent The parent that this view will eventually be attached to
105 | * @return A View corresponding to the data at the specified position.
106 | */
107 | public abstract View getView(int position, View convertView, ViewGroup container);
108 | }
109 |
--------------------------------------------------------------------------------
/app/src/main/java/com/yanshi/my36kr/activity/FavoriteActivity.java:
--------------------------------------------------------------------------------
1 | package com.yanshi.my36kr.activity;
2 |
3 | import android.os.Build;
4 | import android.os.Bundle;
5 | import android.support.v4.app.Fragment;
6 | import android.support.v4.app.FragmentManager;
7 | import android.support.v4.view.ViewPager;
8 | import android.support.v7.app.ActionBar;
9 | import android.support.v7.widget.Toolbar;
10 | import android.view.WindowManager;
11 |
12 | import com.yanshi.my36kr.R;
13 | import com.yanshi.my36kr.adapter.SmartFragmentStatePagerAdapter;
14 | import com.yanshi.my36kr.activity.base.BaseActivity;
15 | import com.yanshi.my36kr.fragment.FavoriteNewsFragment;
16 | import com.yanshi.my36kr.fragment.FavoriteNextFragment;
17 | import com.yanshi.my36kr.common.utils.ScreenUtils;
18 | import com.yanshi.my36kr.common.view.slidingTab.SlidingTabLayout;
19 |
20 | import java.util.ArrayList;
21 | import java.util.List;
22 |
23 | /**
24 | * desc: 我的收藏页
25 | * author: Kinney
26 | * date: 2015/7/7
27 | */
28 | public class FavoriteActivity extends BaseActivity {
29 |
30 | private Toolbar mToolbar;
31 | private final String[] TYPES = new String[]{"文章", "NEXT"};
32 | private List fragmentList;
33 |
34 | @Override
35 | public void onCreate(Bundle savedInstanceState) {
36 | super.onCreate(savedInstanceState);
37 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
38 | getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);// 取消透明导航栏
39 | }
40 | setContentView(R.layout.activity_my_favorite);
41 | findViews();
42 |
43 | fragmentList = new ArrayList<>();
44 | fragmentList.add(null);
45 | fragmentList.add(null);
46 | }
47 |
48 | private void findViews() {
49 | mToolbar = (Toolbar) findViewById(R.id.toolbar);
50 | setSupportActionBar(mToolbar);
51 | ActionBar ab = getSupportActionBar();
52 | if (null != ab) {
53 | ab.setDisplayHomeAsUpEnabled(true);
54 | }
55 |
56 | ViewPager mViewPager = (ViewPager) findViewById(R.id.my_collection_vp);
57 | MyCollectionFragmentPagerAdapter mAdapter = new MyCollectionFragmentPagerAdapter(getSupportFragmentManager());
58 | mViewPager.setAdapter(mAdapter);
59 |
60 | //ViewPager导航器
61 | SlidingTabLayout mSlidingTabLayout = (SlidingTabLayout) findViewById(R.id.my_collection_sliding_tabs);
62 | mSlidingTabLayout.setCustomTabView(R.layout.view_tab_indicator, R.id.tab_indicator_tv);
63 | mSlidingTabLayout.setCustomTabColorizer(new SlidingTabLayout.TabColorizer() {
64 | @Override
65 | public int getIndicatorColor(int position) {
66 | if (position == 0) {
67 | return getResources().getColor(R.color.primary_color);
68 | } else if (position == 1) {
69 | return getResources().getColor(R.color.next_product_title_color);
70 | }
71 | return getResources().getColor(R.color.white);
72 | }
73 |
74 | @Override
75 | public int getDividerColor(int position) {
76 | return getResources().getColor(R.color.half_transparent);
77 | }
78 | });
79 | //设置其宽度为屏幕宽度,官方默认的效果未提供占满屏幕的功能
80 | mSlidingTabLayout.setViewPager(mViewPager, ScreenUtils.getScreenWidth(this));
81 | }
82 |
83 | private class MyCollectionFragmentPagerAdapter extends SmartFragmentStatePagerAdapter {
84 |
85 | public MyCollectionFragmentPagerAdapter(FragmentManager fm) {
86 | super(fm);
87 | }
88 |
89 | @Override
90 | public Fragment getItem(int position) {
91 | if (this.getRegisteredFragment(position) != null) {
92 | return getRegisteredFragment(position);
93 | } else {
94 | Fragment fragment = fragmentList.get(position);
95 | if (null == fragment) {
96 | switch (position) {
97 | case 0:
98 | fragment = new FavoriteNewsFragment();
99 | break;
100 | case 1:
101 | fragment = new FavoriteNextFragment();
102 | break;
103 | }
104 | fragmentList.set(position, fragment);
105 | }
106 | return fragment;
107 | }
108 | }
109 |
110 | @Override
111 | public CharSequence getPageTitle(int position) {
112 | return TYPES[position % TYPES.length];
113 | }
114 |
115 | @Override
116 | public int getCount() {
117 | return TYPES.length;
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
16 |
17 |
32 |
33 |
48 |
49 |
65 |
66 |
77 |
78 |
79 |
88 |
89 |
102 |
103 |
115 |
--------------------------------------------------------------------------------
/app/src/main/java/com/yanshi/my36kr/common/view/DeletableEditText.java:
--------------------------------------------------------------------------------
1 | package com.yanshi.my36kr.common.view;
2 |
3 | import android.content.Context;
4 | import android.graphics.drawable.Drawable;
5 | import android.text.Editable;
6 | import android.text.TextWatcher;
7 | import android.util.AttributeSet;
8 | import android.view.MotionEvent;
9 | import android.view.View;
10 | import android.view.animation.Animation;
11 | import android.view.animation.CycleInterpolator;
12 | import android.view.animation.TranslateAnimation;
13 | import android.widget.EditText;
14 |
15 | /**
16 | * @author kingofglory
17 | * email: kingofglory@yeah.net
18 | * blog: http:www.google.com
19 | * @date 2014-3-13
20 | * TODO
21 | */
22 |
23 | public class DeletableEditText extends EditText{
24 | private Drawable mRightDrawable;
25 | private boolean isHasFocus;
26 |
27 | public DeletableEditText(Context context) {
28 | super(context);
29 | init();
30 | }
31 | public DeletableEditText(Context context, AttributeSet attrs) {
32 | super(context, attrs);
33 | init();
34 | }
35 |
36 | public DeletableEditText(Context context, AttributeSet attrs, int defStyle) {
37 | super(context, attrs, defStyle);
38 | init();
39 | }
40 |
41 | private void init(){
42 | //getCompoundDrawables:
43 | //Returns drawables for the left, top, right, and bottom borders.
44 | Drawable [] drawables=this.getCompoundDrawables();
45 |
46 | //取得right位置的Drawable
47 | //即我们在布局文件中设置的android:drawableRight
48 | mRightDrawable=drawables[2];
49 |
50 | //设置焦点变化的监听
51 | this.setOnFocusChangeListener(new FocusChangeListenerImpl());
52 | //设置EditText文字变化的监听
53 | this.addTextChangedListener(new TextWatcherImpl());
54 | //初始化时让右边clean图标不可见
55 | setClearDrawableVisible(false);
56 | }
57 |
58 |
59 | /**
60 | * 当手指抬起的位置在clean的图标的区域
61 | * 我们将此视为进行清除操作
62 | * getWidth():得到控件的宽度
63 | * event.getX():抬起时的坐标(改坐标是相对于控件本身而言的)
64 | * getTotalPaddingRight():clean的图标左边缘至控件右边缘的距离
65 | * getPaddingRight():clean的图标右边缘至控件右边缘的距离
66 | * 于是:
67 | * getWidth() - getTotalPaddingRight()表示:
68 | * 控件左边到clean的图标左边缘的区域
69 | * getWidth() - getPaddingRight()表示:
70 | * 控件左边到clean的图标右边缘的区域
71 | * 所以这两者之间的区域刚好是clean的图标的区域
72 | */
73 | @Override
74 | public boolean onTouchEvent(MotionEvent event) {
75 | switch (event.getAction()) {
76 | case MotionEvent.ACTION_UP:
77 |
78 | boolean isClean =(event.getX() > (getWidth() - getTotalPaddingRight()))&&
79 | (event.getX() < (getWidth() - getPaddingRight()));
80 | if (isClean) {
81 | setText("");
82 | }
83 | break;
84 |
85 | default:
86 | break;
87 | }
88 | return super.onTouchEvent(event);
89 | }
90 |
91 | private class FocusChangeListenerImpl implements OnFocusChangeListener{
92 | @Override
93 | public void onFocusChange(View v, boolean hasFocus) {
94 | isHasFocus=hasFocus;
95 | if (isHasFocus) {
96 | boolean isVisible=getText().toString().length()>=1;
97 | setClearDrawableVisible(isVisible);
98 | } else {
99 | setClearDrawableVisible(false);
100 | }
101 | }
102 |
103 | }
104 |
105 | //当输入结束后判断是否显示右边clean的图标
106 | private class TextWatcherImpl implements TextWatcher{
107 | @Override
108 | public void afterTextChanged(Editable s) {
109 | boolean isVisible=getText().toString().length()>=1;
110 | setClearDrawableVisible(isVisible);
111 | }
112 |
113 | @Override
114 | public void beforeTextChanged(CharSequence s, int start, int count,int after) {
115 |
116 | }
117 |
118 | @Override
119 | public void onTextChanged(CharSequence s, int start, int before,int count) {
120 |
121 | }
122 |
123 | }
124 |
125 | //隐藏或者显示右边clean的图标
126 | protected void setClearDrawableVisible(boolean isVisible) {
127 | Drawable rightDrawable;
128 | if (isVisible) {
129 | rightDrawable = mRightDrawable;
130 | } else {
131 | rightDrawable = null;
132 | }
133 | //使用代码设置该控件left, top, right, and bottom处的图标
134 | setCompoundDrawables(getCompoundDrawables()[0],getCompoundDrawables()[1],
135 | rightDrawable,getCompoundDrawables()[3]);
136 | }
137 |
138 | // 显示一个动画,以提示用户输入
139 | public void setShakeAnimation() {
140 | this.startAnimation(shakeAnimation(5));
141 |
142 | }
143 |
144 | //CycleTimes动画重复的次数
145 | public Animation shakeAnimation(int CycleTimes) {
146 | Animation translateAnimation = new TranslateAnimation(0, 10, 0, 10);
147 | translateAnimation.setInterpolator(new CycleInterpolator(CycleTimes));
148 | translateAnimation.setDuration(1000);
149 | return translateAnimation;
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jakewharton/salvage/RecycleBin.java:
--------------------------------------------------------------------------------
1 | package com.jakewharton.salvage;
2 |
3 | import android.os.Build;
4 | import android.util.SparseArray;
5 | import android.view.View;
6 |
7 | /**
8 | * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
9 | * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
10 | * start of a layout. By construction, they are displaying current information. At the end of
11 | * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
12 | * could potentially be used by the adapter to avoid allocating views unnecessarily.
13 | *
14 | * This class was taken from Android's implementation of {@link android.widget.AbsListView} which
15 | * is copyrighted 2006 The Android Open Source Project.
16 | */
17 | public class RecycleBin {
18 | /**
19 | * Views that were on screen at the start of layout. This array is populated at the start of
20 | * layout, and at the end of layout all view in activeViews are moved to scrapViews.
21 | * Views in activeViews represent a contiguous range of Views, with position of the first
22 | * view store in mFirstActivePosition.
23 | */
24 | private View[] activeViews = new View[0];
25 | private int[] activeViewTypes = new int[0];
26 |
27 | /** Unsorted views that can be used by the adapter as a convert view. */
28 | private SparseArray[] scrapViews;
29 |
30 | private int viewTypeCount;
31 |
32 | private SparseArray currentScrapViews;
33 |
34 | public void setViewTypeCount(int viewTypeCount) {
35 | if (viewTypeCount < 1) {
36 | throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
37 | }
38 | //noinspection unchecked
39 | SparseArray[] scrapViews = new SparseArray[viewTypeCount];
40 | for (int i = 0; i < viewTypeCount; i++) {
41 | scrapViews[i] = new SparseArray();
42 | }
43 | this.viewTypeCount = viewTypeCount;
44 | currentScrapViews = scrapViews[0];
45 | this.scrapViews = scrapViews;
46 | }
47 |
48 | protected boolean shouldRecycleViewType(int viewType) {
49 | return viewType >= 0;
50 | }
51 |
52 | /** @return A view from the ScrapViews collection. These are unordered. */
53 | View getScrapView(int position, int viewType) {
54 | if (viewTypeCount == 1) {
55 | return retrieveFromScrap(currentScrapViews, position);
56 | } else if (viewType >= 0 && viewType < scrapViews.length) {
57 | return retrieveFromScrap(scrapViews[viewType], position);
58 | }
59 | return null;
60 | }
61 |
62 | /**
63 | * Put a view into the ScrapViews list. These views are unordered.
64 | *
65 | * @param scrap The view to add
66 | */
67 | void addScrapView(View scrap, int position, int viewType) {
68 | if (viewTypeCount == 1) {
69 | currentScrapViews.put(position, scrap);
70 | } else {
71 | scrapViews[viewType].put(position, scrap);
72 | }
73 |
74 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
75 | scrap.setAccessibilityDelegate(null);
76 | }
77 | }
78 |
79 | /** Move all views remaining in activeViews to scrapViews. */
80 | void scrapActiveViews() {
81 | final View[] activeViews = this.activeViews;
82 | final int[] activeViewTypes = this.activeViewTypes;
83 | final boolean multipleScraps = viewTypeCount > 1;
84 |
85 | SparseArray scrapViews = currentScrapViews;
86 | final int count = activeViews.length;
87 | for (int i = count - 1; i >= 0; i--) {
88 | final View victim = activeViews[i];
89 | if (victim != null) {
90 | int whichScrap = activeViewTypes[i];
91 |
92 | activeViews[i] = null;
93 | activeViewTypes[i] = -1;
94 |
95 | if (!shouldRecycleViewType(whichScrap)) {
96 | continue;
97 | }
98 |
99 | if (multipleScraps) {
100 | scrapViews = this.scrapViews[whichScrap];
101 | }
102 | scrapViews.put(i, victim);
103 |
104 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
105 | victim.setAccessibilityDelegate(null);
106 | }
107 | }
108 | }
109 |
110 | pruneScrapViews();
111 | }
112 |
113 | /**
114 | * Makes sure that the size of scrapViews does not exceed the size of activeViews.
115 | * (This can happen if an adapter does not recycle its views).
116 | */
117 | private void pruneScrapViews() {
118 | final int maxViews = activeViews.length;
119 | final int viewTypeCount = this.viewTypeCount;
120 | final SparseArray[] scrapViews = this.scrapViews;
121 | for (int i = 0; i < viewTypeCount; ++i) {
122 | final SparseArray scrapPile = scrapViews[i];
123 | int size = scrapPile.size();
124 | final int extras = size - maxViews;
125 | size--;
126 | for (int j = 0; j < extras; j++) {
127 | scrapPile.remove(scrapPile.keyAt(size--));
128 | }
129 | }
130 | }
131 |
132 | static View retrieveFromScrap(SparseArray scrapViews, int position) {
133 | int size = scrapViews.size();
134 | if (size > 0) {
135 | // See if we still have a view for this position.
136 | for (int i = 0; i < size; i++) {
137 | int fromPosition = scrapViews.keyAt(i);
138 | View view = scrapViews.get(fromPosition);
139 | if (fromPosition == position) {
140 | scrapViews.remove(fromPosition);
141 | return view;
142 | }
143 | }
144 | int index = size - 1;
145 | View r = scrapViews.valueAt(index);
146 | scrapViews.remove(scrapViews.keyAt(index));
147 | return r;
148 | } else {
149 | return null;
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/app/src/main/java/com/yanshi/my36kr/biz/NextItemBiz.java:
--------------------------------------------------------------------------------
1 | package com.yanshi.my36kr.biz;
2 |
3 | import android.os.Handler;
4 | import android.os.Message;
5 |
6 | import com.yanshi.my36kr.bean.Constant;
7 | import com.yanshi.my36kr.bean.NextItem;
8 |
9 | import org.jsoup.Jsoup;
10 | import org.jsoup.nodes.Document;
11 | import org.jsoup.select.Elements;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | /**
17 | * 解析html字符串,获取NEXT栏目的feed流
18 | * Created by kingars on 2014/10/29.
19 | */
20 | public class NextItemBiz {
21 |
22 | private static List list;
23 |
24 | private static OnParseListener onParseListener;
25 |
26 | private static final int MSG_SUCC = 1;
27 | private static final int MSG_FAIL = 0;
28 |
29 | private static DispatchStatusHandler mHandler = new DispatchStatusHandler();
30 |
31 | private static class DispatchStatusHandler extends Handler {
32 | @Override
33 | public void handleMessage(Message msg) {
34 | switch (msg.what) {
35 | case MSG_SUCC:
36 | if (null != onParseListener) onParseListener.onParseSuccess(list);
37 | break;
38 | case MSG_FAIL:
39 | if (null != onParseListener) onParseListener.onParseFailed();
40 | break;
41 | }
42 | }
43 | }
44 |
45 | public static void getFeed(final String html, OnParseListener onParseListener) {
46 | NextItemBiz.onParseListener = onParseListener;
47 | new Thread() {
48 | @Override
49 | public void run() {
50 | Document doc = Jsoup.parse(html, Constant.NEXT_URL);
51 | if (null == doc) {
52 | mHandler.sendEmptyMessage(MSG_FAIL);
53 | return;
54 | }
55 | if (list == null) {
56 | list = new ArrayList();
57 | } else {
58 | list.clear();
59 | }
60 |
61 | Elements product_ele = doc.getElementsByClass("product-item");
62 | for (int i = 0; i < product_ele.size(); i++) {
63 | NextItem nextItem = new NextItem();
64 | //投票数
65 | Elements vote_ele = product_ele.get(i).getElementsByClass("vote-count");
66 | int voteCount = 0;
67 | if (vote_ele.size() != 0) voteCount = Integer.parseInt(vote_ele.get(0).text());
68 |
69 | nextItem.setVoteCount(voteCount);
70 |
71 | //链接、标题
72 | Elements post_ele = product_ele.get(i).getElementsByClass("post-url");
73 | String url = "";
74 | String title = "";
75 | if (post_ele.size() != 0) {
76 | url = post_ele.get(0).attr("href").replace("/hit", "");
77 | title = post_ele.get(0).text();
78 | }
79 | if (!url.startsWith("http://")) {
80 | url = Constant.NEXT_URL + url;
81 | }
82 |
83 | nextItem.setUrl(url);
84 | nextItem.setTitle(title);
85 |
86 | //内容
87 | Elements content_ele = product_ele.get(i).getElementsByClass("post-tagline");
88 | String content = "";
89 | if (content_ele.size() != 0) content = content_ele.get(0).text();
90 |
91 | nextItem.setContent(content);
92 |
93 | //评论数
94 | Elements comment_ele = product_ele.get(i).getElementsByClass("product-comment");
95 | int commentCount = 0;
96 | if (comment_ele.size() != 0)
97 | commentCount = Integer.parseInt(comment_ele.get(0).text());
98 |
99 | nextItem.setCommentCount(commentCount);
100 |
101 |
102 | list.add(nextItem);
103 | }
104 |
105 | Elements post_ele = doc.getElementsByClass("post");
106 | //三个日期的字符串
107 | String[] days = new String[3];
108 | //每个日期下的条目数
109 | int[] perCount = new int[3];
110 | for (int i = 0; i < post_ele.size(); i++) {
111 | days[i] = post_ele.get(i).getElementsByTag("small").text();
112 | Elements item_ele = post_ele.get(i).getElementsByClass("product-item");
113 | perCount[i] = item_ele.size();
114 |
115 | }
116 | //判断三个日期的条目总数量是否等于之前解析所有条目的数量
117 | int totalCount = perCount[0] + perCount[1] + perCount[2];
118 | if (list.size() == totalCount) {
119 | for (int i = 0; i < totalCount; i++) {
120 | if (i <= perCount[0] - 1) {
121 | list.get(i).setDate(days[0]);
122 | } else if (i <= totalCount - perCount[2] - 1) {
123 | list.get(i).setDate(days[1]);
124 | } else if (i <= totalCount - 1) {
125 | list.get(i).setDate(days[2]);
126 | }
127 | }
128 | }
129 |
130 | if (!list.isEmpty()) {
131 | mHandler.sendEmptyMessage(MSG_SUCC);
132 | } else {
133 | mHandler.sendEmptyMessage(MSG_FAIL);
134 | }
135 | }
136 | }.start();
137 | }
138 |
139 | }
140 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/src/main/java/com/yanshi/my36kr/common/view/HeadlinesView.java:
--------------------------------------------------------------------------------
1 | package com.yanshi.my36kr.common.view;
2 |
3 | import android.content.Context;
4 | import android.os.Handler;
5 | import android.support.v4.view.PagerAdapter;
6 | import android.support.v4.view.ViewPager;
7 | import android.util.AttributeSet;
8 | import android.view.LayoutInflater;
9 | import android.view.MotionEvent;
10 | import android.widget.FrameLayout;
11 | import android.widget.TextView;
12 |
13 | import com.antonyt.infiniteviewpager.InfinitePagerAdapter;
14 | import com.antonyt.infiniteviewpager.InfiniteViewPager;
15 | import com.yanshi.my36kr.R;
16 | import com.yanshi.my36kr.adapter.index.ImagePagerAdapter;
17 | import com.yanshi.my36kr.bean.NewsItem;
18 |
19 | import java.util.ArrayList;
20 | import java.util.List;
21 |
22 | /**
23 | * auto scroll infinite ViewPager with a title
24 | * include an open source project: InfiniteViewPager
25 | * Created by kinneyYan on 2015/07/01.
26 | */
27 | public class HeadlinesView extends FrameLayout {
28 |
29 | private int scrollDelayTime = 3000;//自动滑动的间隔时间
30 | private boolean isAutoScroll = true;//是否自动滑动
31 | private boolean isScrolling = false;//是否正在自动滑动
32 |
33 | private List list = new ArrayList<>();//数据集
34 | private InfiniteViewPager mViewPager;
35 | private PagerAdapter mPagerAdapter;
36 | private TextView titleTv;
37 | private TextView numberTv;
38 |
39 | private Handler mHandler;
40 |
41 | /**
42 | * 初始化数据
43 | *
44 | * @param feedList
45 | */
46 | public void refreshData(List feedList) {
47 | if (null == feedList || feedList.isEmpty()) return;
48 | list.clear();
49 | list.addAll(feedList);
50 |
51 | mPagerAdapter = new InfinitePagerAdapter(new ImagePagerAdapter(getContext(), list));
52 | mViewPager.setAdapter(mPagerAdapter);
53 |
54 | titleTv.setText(list.get(0 % list.size()).getTitle());
55 | numberTv.setText(new StringBuilder().append((0) % list.size() + 1).append("/")
56 | .append(list.size()));
57 |
58 | this.setVisibility(VISIBLE);
59 | }
60 |
61 | /**
62 | * 开始自动滑动 used in onResume()
63 | */
64 | public void startAutoScroll() {
65 | if (isAutoScroll && !isScrolling && null != mHandler) {
66 | mHandler.postDelayed(autoScrollTask, scrollDelayTime);
67 | isScrolling = true;
68 | }
69 | }
70 |
71 | /**
72 | * 停止自动滑动 used in onPause()
73 | */
74 | public void stopAutoScroll() {
75 | if (null != mHandler) {
76 | mHandler.removeCallbacks(autoScrollTask);
77 | isScrolling = false;
78 | }
79 | }
80 |
81 | /**
82 | * 设置是否自动滑动
83 | *
84 | * @param isAutoScroll
85 | */
86 | public void setAutoScroll(boolean isAutoScroll) {
87 | this.isAutoScroll = isAutoScroll;
88 | }
89 |
90 | /**
91 | * 设置自动滑动的间隔时间
92 | *
93 | * @param time_scroll_delay
94 | */
95 | public void setAutoScrollDelay(int time_scroll_delay) {
96 | this.scrollDelayTime = time_scroll_delay;
97 | }
98 |
99 | public HeadlinesView(Context context) {
100 | super(context);
101 | init(context);
102 | }
103 |
104 | public HeadlinesView(Context context, AttributeSet attrs) {
105 | super(context, attrs);
106 | init(context);
107 | }
108 |
109 | public HeadlinesView(Context context, AttributeSet attrs, int defStyle) {
110 | super(context, attrs, defStyle);
111 | init(context);
112 | }
113 |
114 | private void init(Context context) {
115 | LayoutInflater inflater = LayoutInflater.from(context);
116 | inflater.inflate(R.layout.view_headlines, this);
117 | findViews();
118 | mHandler = new Handler();
119 |
120 | mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
121 | @Override
122 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
123 | }
124 |
125 | @Override
126 | public void onPageSelected(int position) {
127 | titleTv.setText(list.get(position % list.size()).getTitle());
128 | numberTv.setText(new StringBuilder().append((position) % list.size() + 1).append("/")
129 | .append(list.size()));
130 | }
131 |
132 | @Override
133 | public void onPageScrollStateChanged(int state) {
134 | }
135 | });
136 |
137 | this.setVisibility(GONE);
138 | }
139 |
140 | private void findViews() {
141 | mViewPager = (InfiniteViewPager) findViewById(R.id.headlines_view_vp);
142 | titleTv = (TextView) findViewById(R.id.headlines_view_title_tv);
143 | numberTv = (TextView) findViewById(R.id.headlines_view_number_tv);
144 | }
145 |
146 | private Runnable autoScrollTask = new Runnable() {
147 | @Override
148 | public void run() {
149 | int currentItem = mViewPager.getSuperCurrentItem();
150 | currentItem++;
151 | if (Integer.MAX_VALUE == currentItem) currentItem = 0;
152 | mViewPager.setSuperCurrentItem(currentItem);
153 |
154 | mHandler.postDelayed(this, scrollDelayTime);
155 | }
156 | };
157 |
158 | @Override
159 | public boolean dispatchTouchEvent(MotionEvent ev) {
160 | switch (ev.getAction()) {
161 | case MotionEvent.ACTION_DOWN:
162 | stopAutoScroll();
163 | break;
164 | case MotionEvent.ACTION_CANCEL:
165 | case MotionEvent.ACTION_UP:
166 | startAutoScroll();
167 | break;
168 | }
169 | return super.dispatchTouchEvent(ev);
170 | }
171 |
172 | }
173 |
--------------------------------------------------------------------------------
/app/src/main/java/com/yanshi/my36kr/biz/NewsItemBiz.java:
--------------------------------------------------------------------------------
1 | package com.yanshi.my36kr.biz;
2 |
3 | import android.os.Handler;
4 | import android.os.Message;
5 |
6 | import com.yanshi.my36kr.bean.Constant;
7 | import com.yanshi.my36kr.bean.NewsItem;
8 |
9 | import org.jsoup.Jsoup;
10 | import org.jsoup.nodes.Document;
11 | import org.jsoup.nodes.Element;
12 | import org.jsoup.select.Elements;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | /**
18 | * 解析html字符串,获取头条、feed流的业务类
19 | * 作者:yanshi
20 | * 时间:2015-06-30 14:59
21 | */
22 | public class NewsItemBiz {
23 |
24 | private static List list;
25 |
26 | private static OnParseListener onParseListener;
27 |
28 | public static final int MSG_SUCC = 1;
29 | public static final int MSG_FAIL = 0;
30 |
31 | private static DispatchStatusHandler mHandler = new DispatchStatusHandler();
32 |
33 | private static class DispatchStatusHandler extends Handler {
34 | @Override
35 | public void handleMessage(Message msg) {
36 | switch (msg.what) {
37 | case MSG_SUCC:
38 | if (null != onParseListener) onParseListener.onParseSuccess(list);
39 | break;
40 | case MSG_FAIL:
41 | if (null != onParseListener) onParseListener.onParseFailed();
42 | break;
43 | }
44 | }
45 | }
46 |
47 | public static void getFeed(final String html, OnParseListener onParseListener) {
48 | NewsItemBiz.onParseListener = onParseListener;
49 | new Thread() {
50 | @Override
51 | public void run() {
52 | Document doc = Jsoup.parse(html, Constant.INDEX_URL);
53 | if (null == doc) {
54 | mHandler.sendEmptyMessage(MSG_FAIL);
55 | return;
56 | }
57 | if (list == null) {
58 | list = new ArrayList();
59 | } else {
60 | list.clear();
61 | }
62 |
63 | //1-获取头条内容
64 | //jsoup使用样式class抓取数据时空格的处理:http://www.cnblogs.com/l_dragon/archive/2013/08/27/jsoup.html
65 | Elements elements1 = doc.select(".scrollers");
66 | handleHeadLinesElements(list, elements1);
67 | Elements elements2 = doc.select(".block").select(".article");
68 | handleHeadLinesElements(list, elements2);
69 |
70 | //2-获取feed流内容
71 | Elements elements = doc.getElementsByTag("article");
72 | handleFeedElements(list, elements);
73 |
74 | if (list.size() > 4) {
75 | mHandler.sendEmptyMessage(MSG_SUCC);// 确保回调的传过去的list符合条件
76 | } else {
77 | mHandler.sendEmptyMessage(MSG_FAIL);
78 | }
79 | }
80 | }.start();
81 | }
82 |
83 | private static void handleHeadLinesElements(List headLineList, Elements elements) {
84 | if (null != headLineList && elements.size() > 0) {
85 | for (int i = 0; i < elements.size(); i++) {
86 | NewsItem item = new NewsItem();
87 | Element element = elements.get(i);
88 | //标题
89 | Elements elementsSpan = element.getElementsByTag("span");
90 | if (elementsSpan.size() > 0) {
91 | item.setTitle(elementsSpan.text());
92 | }
93 | //链接、图片
94 | Elements elementsLink = element.getElementsByTag("a");
95 | if (elementsLink.size() > 0) {
96 | item.setUrl(elementsLink.attr("href"));
97 | item.setImgUrl(elementsLink.attr("data-lazyload"));
98 | }
99 |
100 | headLineList.add(item);
101 | }
102 | }
103 | }
104 |
105 | private static void handleFeedElements(List feedList, Elements elements) {
106 | if (null != feedList && elements.size() > 0) {
107 | for (int i = 0; i < elements.size(); i++) {
108 | NewsItem item = new NewsItem();
109 | Element element = elements.get(i);
110 | //标题、链接 class="title info_flow_news_title"
111 | Elements elementsTitle = element.select(".title").select(".info_flow_news_title");
112 | if (elementsTitle.size() > 0) {
113 | item.setTitle(elementsTitle.text());
114 |
115 | String url = elementsTitle.attr("href");
116 | if (url != null && !url.startsWith("http")) {// 针对相对链接时加上host
117 | url = Constant.INDEX_URL + elementsTitle.attr("href");
118 | }
119 | item.setUrl(url);
120 | }
121 | //图片 class="pic info_flow_news_image"
122 | Elements elementsPic = element.select(".pic").select(".info_flow_news_image");
123 | if (elementsPic.size() > 0) {
124 | item.setImgUrl(elementsPic.attr("data-lazyload"));
125 | }
126 | //时间 class="timeago"
127 | Elements elementsTime = element.getElementsByClass("timeago");
128 | if (elementsTime.size() > 0) {
129 | item.setDate(elementsTime.text());
130 | }
131 | //概要 class="brief"
132 | Elements elementsBrief = element.getElementsByClass("brief");
133 | if (elementsBrief.size() > 0) {
134 | item.setContent(elementsBrief.text());
135 | }
136 |
137 | if (null == item.getTitle() && null == item.getUrl() && null == item.getImgUrl() && null == item.getDate()
138 | && null == item.getContent()) continue;//过滤掉web端的广告
139 | feedList.add(item);
140 | }
141 | }
142 | }
143 |
144 | }
145 |
--------------------------------------------------------------------------------
/app/src/main/java/com/yanshi/my36kr/common/utils/HttpUtils.java:
--------------------------------------------------------------------------------
1 | package com.yanshi.my36kr.common.utils;
2 |
3 | import java.io.*;
4 | import java.net.HttpURLConnection;
5 | import java.net.URL;
6 |
7 | /**
8 | * Http相关辅助类
9 | * Created by kingars on 2014/10/25.
10 | */
11 | public class HttpUtils {
12 |
13 | private static final int TIMEOUT_IN_MILLIONS = 5000;
14 |
15 | public interface CallBack {
16 | void onRequestComplete(String result);
17 | }
18 |
19 |
20 | /**
21 | * 异步的Get请求
22 | *
23 | * @param urlStr
24 | * @param callBack
25 | */
26 | public static void doGetAsyn(final String urlStr, final CallBack callBack) {
27 | new Thread() {
28 | public void run() {
29 | try {
30 | String result = doGet(urlStr);
31 | if (callBack != null) {
32 | callBack.onRequestComplete(result);
33 | }
34 | } catch (Exception e) {
35 | e.printStackTrace();
36 | }
37 |
38 | }
39 |
40 | ;
41 | }.start();
42 | }
43 |
44 | /**
45 | * 异步的Post请求
46 | *
47 | * @param urlStr
48 | * @param params
49 | * @param callBack
50 | * @throws Exception
51 | */
52 | public static void doPostAsyn(final String urlStr, final String params,
53 | final CallBack callBack) throws Exception {
54 | new Thread() {
55 | public void run() {
56 | try {
57 | String result = doPost(urlStr, params);
58 | if (callBack != null) {
59 | callBack.onRequestComplete(result);
60 | }
61 | } catch (Exception e) {
62 | e.printStackTrace();
63 | }
64 |
65 | }
66 |
67 | ;
68 | }.start();
69 |
70 | }
71 |
72 | /**
73 | * Get请求,获得返回数据
74 | *
75 | * @param urlStr
76 | * @return
77 | * @throws Exception
78 | */
79 | public static String doGet(String urlStr) {
80 | URL url = null;
81 | HttpURLConnection conn = null;
82 | InputStream is = null;
83 | ByteArrayOutputStream baos = null;
84 | try {
85 | url = new URL(urlStr);
86 | conn = (HttpURLConnection) url.openConnection();
87 | conn.setReadTimeout(TIMEOUT_IN_MILLIONS);
88 | conn.setConnectTimeout(TIMEOUT_IN_MILLIONS);
89 | conn.setRequestMethod("GET");
90 | conn.setRequestProperty("accept", "*/*");
91 | conn.setRequestProperty("connection", "Keep-Alive");
92 | if (conn.getResponseCode() == 200) {
93 | is = conn.getInputStream();
94 | baos = new ByteArrayOutputStream();
95 | int len = -1;
96 | byte[] buf = new byte[1024];
97 |
98 | while ((len = is.read(buf)) != -1) {
99 | baos.write(buf, 0, len);
100 | }
101 | baos.flush();
102 | return baos.toString();
103 | } else {
104 | throw new RuntimeException(" responseCode is not 200 ... ");
105 | }
106 |
107 | } catch (Exception e) {
108 | e.printStackTrace();
109 | } finally {
110 | try {
111 | if(is != null) {
112 | is.close();
113 | }
114 | if(baos != null) {
115 | baos.close();
116 | }
117 | } catch (IOException e) {
118 | e.printStackTrace();
119 | }
120 | if (conn != null) {
121 | conn.disconnect();
122 | }
123 | }
124 |
125 | return null;
126 |
127 | }
128 |
129 | /**
130 | * 向指定 URL 发送POST方法的请求
131 | *
132 | * @param url 发送请求的 URL
133 | * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
134 | * @return 所代表远程资源的响应结果
135 | * @throws Exception
136 | */
137 | public static String doPost(String url, String param) {
138 | PrintWriter out = null;
139 | BufferedReader in = null;
140 | String result = "";
141 | try {
142 | URL realUrl = new URL(url);
143 | // 打开和URL之间的连接
144 | HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();
145 | // 设置通用的请求属性
146 | conn.setRequestProperty("accept", "*/*");
147 | conn.setRequestProperty("connection", "Keep-Alive");
148 | conn.setRequestMethod("POST");
149 | conn.setRequestProperty("Content-Type",
150 | "application/x-www-form-urlencoded");
151 | conn.setRequestProperty("charset", "utf-8");
152 | conn.setUseCaches(false);
153 | // 发送POST请求必须设置如下两行
154 | conn.setDoOutput(true);
155 | conn.setDoInput(true);
156 | conn.setReadTimeout(TIMEOUT_IN_MILLIONS);
157 | conn.setConnectTimeout(TIMEOUT_IN_MILLIONS);
158 |
159 | if (param != null && !param.trim().equals("")) {
160 | // 获取URLConnection对象对应的输出流
161 | out = new PrintWriter(conn.getOutputStream());
162 | // 发送请求参数
163 | out.print(param);
164 | // flush输出流的缓冲
165 | out.flush();
166 | }
167 | // 定义BufferedReader输入流来读取URL的响应
168 | in = new BufferedReader(
169 | new InputStreamReader(conn.getInputStream()));
170 | String line;
171 | while ((line = in.readLine()) != null) {
172 | result += line;
173 | }
174 | } catch (Exception e) {
175 | e.printStackTrace();
176 | }
177 | // 使用finally块来关闭输出流、输入流
178 | finally {
179 | try {
180 | if (out != null) {
181 | out.close();
182 | }
183 | if (in != null) {
184 | in.close();
185 | }
186 | } catch (IOException e) {
187 | e.printStackTrace();
188 | }
189 | }
190 | return result;
191 | }
192 | }
193 |
--------------------------------------------------------------------------------