开源中国源码分析(一)

开源中国源码地址:http://git.oschina.net/oschina/android-app

咨讯详情页面分析

布局页面文件fragment_general_news_detail.xml布局文件中包含上滑评论条隐藏,下滑评论条显示

相关推荐、相关评论布局是通过继承LinearLayout再填充marge布局页面

相关推荐布局文件lay_detail_about_layout.xml,看运行效果貌似是ListView,

其实是LineartLayout,由DetailAboutView类的如下代码实现

详情页面文件NewsDetailActivity.java其继承于DetailAvtivity.java,DetailActivity.java的布局文件中帧布局父容器包含一个帧布局和一个自定义的EmptyLayout。基类中onCreate()方法中的initWindow()、initWidget()、initData();getContentView()、getImageLoader()

public abstract class DetailActivity<Data, DataView extends DetailContract.View> extends BaseBaceActivity implements DetailContract.Operator<Data, DataView> 有2个泛型的属性

private Data mData; 

private DataView mView;

数据的加载是在NewsDetailActivity.java文件中,数据的展示是在NewsDetailFragment.java文件中

在DetailFragment.java的如下代码关联了数据的获取和数据的展示

public void onAttach(Context context) {

this.mOperator = (Operator) context;

this.mOperator.setDataView((DataView) this);

super.onAttach(context);

}


public interface DetailContract 、 public interface NewsDetailContract 同时被NewsDetaiActivity 和 NewsDetailFragment实现

数据在NewsDetailActivity.java文件中请求成功后才会通过handleView()方法填充NewsDetailFragment.java文件


NewsDetailFragment.java


CommentsView.java



对于评论中的他人跟随的评论CommentsUtil.java

综合 -> 资讯 -> 博客详情页面 底部评论工具条上滑消失,下滑出现的实现方式:

1. 根布局是CoordinatorLayout

2. 上面滑动布局的根布局是NestedScrollView

3. 在底部评论工具条的容器布局中添加一个属性app:layout_behavior="FloatingHideDownBehavior类的全路径"


public class FloatingAutoHideDownBehavior extends CoordinatorLayout.Behavior<View> {
    private static final Interpolator INTERPOLATOR = new DecelerateInterpolator();
    private boolean mIsAnimatingOut = false;   // 动画已经显示标识
    private boolean mIsScrollToBottom = false;

    public FloatingAutoHideDownBehavior(Context context, AttributeSet attrs) {
        super();
    }

    /**

    * onStartNestedScroll返回true时被调用

    * 由上往下滑动dy<0并且滑动的力度越大dy越小;由下往上滑动dy>0并且滑动的力度越大dy越大。

    */
    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);


        // 手指下滑dy < 0     工具条隐藏时 translationY = child.height
        if (!mIsScrollToBottom) {
            float mPreTranslationY = dy + child.getTranslationY();
            if (mPreTranslationY <= 0) {
                child.setTranslationY(0);
                mIsAnimatingOut = true;
            }
            if (mPreTranslationY >= child.getHeight()) {
                child.setTranslationY(child.getHeight());
                mIsAnimatingOut = false;
            }
            if (mPreTranslationY > 0 && mPreTranslationY < child.getHeight()) {
                child.setTranslationY(mPreTranslationY);
                mIsAnimatingOut = dy > 0;
            }
        }
    }

    /**

    *  判断是否是所依赖的view。例如:return dependency instanceof ListView;

  * 判断child依赖的dependency是否是ListView 只有返回true时才执行onDependentViewChanged方法

    */
    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, final View child, View dependency) {
        if (child != null && dependency != null && dependency instanceof NestedScrollView) {
            NestedScrollView s = (NestedScrollView) dependency;
            if (s.getChildCount() > 0) {
                // 嵌套布局底部的控件有个底部工具条控件高度的paddingBottom
                View view = s.getChildAt(s.getChildCount() - 1);
                view.setPadding(view.getPaddingLeft(),
                        view.getPaddingTop(),
                        view.getPaddingRight(),
                        view.getPaddingBottom() + child.getHeight());
            }


            s.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
                @Override
                public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                    if (v.getChildCount() > 0) {
                        // Grab the last child placed in the ScrollView, we need it to determinate the bottom position.
                        View view = v.getChildAt(v.getChildCount() - 1);
                        // Calculate the scrolldiff
                        int diff = (view.getBottom() - (v.getHeight() + scrollY));
                        // if diff is zero, then the bottom has been reached
                        Log.d("@@", "view.bottom = " + view.getBottom() + " , scrollY = " + scrollY);
                        if (diff == 0) {   // 滑动到底部时,显示工具条
                            // notify that we have reached the bottom
                            animateIn(child);
                            mIsScrollToBottom = true;
                        } else {
                            mIsScrollToBottom = false;
                        }
                    }
                }
            });
        }
        return super.layoutDependsOn(parent, child, dependency);
    }

    /**

    *  只要滑动就会被调用,注意被依赖的view需要设置setNestedScrollingEnabled(true)可以判断滑动方向,比   * 如:(nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0 判断滑动方向是否为垂直方向

    */
    @Override
    public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final View child,
                                       final View directTargetChild, final View target, final int nestedScrollAxes) {
        // 滑动时隐藏软键盘
        TDevice.hideSoftKeyboard(coordinatorLayout);


        // Ensure we react to vertical scrolling 判断是否是垂直滑动方向
        return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
                || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
    }


    @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
        super.onStopNestedScroll(coordinatorLayout, child, target);
        // 完全显示或者完全隐藏 则返回
        if (child.getTranslationY() == 0 || child.getTranslationY() == child.getHeight()) return;

        if (mIsAnimatingOut) {
            animateOut(child);
        } else {
            animateIn(child);
        }
    }


    /***
     * 隐藏底部工具条
     * @param button
     */
    private void animateOut(final View button) {
        button.animate()
                .translationY(button.getHeight())
                .setInterpolator(INTERPOLATOR)
                .setDuration(200)
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        button.setTranslationY(button.getHeight());
                    }
                });
    }

    /**
     * 显示底部工具条
     * @param button
     */
    private void animateIn(final View button) {
        button.animate()
                .translationY(0)
                .setInterpolator(INTERPOLATOR)
                .setDuration(200)
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        button.setTranslationY(0);
                    }
                });
    }

    /**
     * 点击内容栏唤起底部操作区域
     * @param coordinatorLayout 外部CoordinatorLayout
     * @param contentView       滚动区域
     * @param bottomView        滚动时隐藏底部区域
     */
    public static void showBottomLayout(CoordinatorLayout coordinatorLayout, View contentView, final View bottomView) {
        bottomView.animate()
                .translationY(0)
                .setInterpolator(INTERPOLATOR)
                .setDuration(200)
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        bottomView.setTranslationY(0);
                    }
                });
    }
}

咨讯详情导航栏右上角带有数字图标的点击事件执行方法

public void scrollToComment() {
        NestedScrollView nestedScrollView = mScrollView;
        View target = mScrollTargetView;
        if (nestedScrollView != null && target != null) {
            // 默认scrollY是0, 当手指上滑的过程中scrollY变大
            int curY = nestedScrollView.getScrollY();
            int targetY = target.getTop();
            Log.e("TAG", "curY:" + curY + " targetY:" + targetY + " mScrollYPoint:" + mScrollYPoint);
            if (targetY > 0) { // 具有评论
                if (curY == targetY && targetY == mScrollYPoint) {
                    nestedScrollView.fullScroll(View.FOCUS_UP);
                } else {
                    if (mScrollYPoint == -1) {
                        nestedScrollView.smoothScrollTo(0, targetY);
                        mScrollYPoint = curY;
                        return;
                    }
                    if (curY > targetY) {
                        // 当前在评论之后
                        if (mScrollYPoint < targetY) {
                            nestedScrollView.smoothScrollTo(0, mScrollYPoint);
                        } else {
                            nestedScrollView.fullScroll(View.FOCUS_UP);
                        }
                        mScrollYPoint = curY;
                    } else {
                        // 当前在评论之前
                        nestedScrollView.smoothScrollTo(0, mScrollYPoint);
                        if (mScrollYPoint < curY) {
                            mScrollYPoint = -1;
                        } else {
                            mScrollYPoint = 0;
                        }
                    }
                }
            } else { // 没有评论时的逻辑
                if (mScrollYPoint == -1) {
                    nestedScrollView.fullScroll(View.FOCUS_DOWN); // 滑动到底部,并且记录当前滑动的距离
                    mScrollYPoint = curY;
                } else {
                    nestedScrollView.smoothScrollTo(0, mScrollYPoint); // 定位到先前的位置
                    mScrollYPoint = -1;
                }
            }
        }
    }


活动详情EventFragment.java

底部收藏工具条的收藏按钮的背景点击特效实现

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape><solid android:color="#eeeeee"/></shape>
    </item>
    <item>
        <shape><solid android:color="@color/white"/></shape>
    </item>
</selector>

对应网页内容的显示使用了自定义的OWebView加载字符串


BlogFragment.java如下效果的实现方式,给按钮添加个背景,按钮布局是作为GridView的itemView,再将GridView作为ListView的头布局


<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_activated="false">
        <shape>
            <corners android:radius="2dp" />
            <solid android:color="@color/ques_bt_bg" />
            <stroke android:width="1px" android:color="@android:color/transparent" />
        </shape>
    </item>
    <item android:state_activated="true">
        <shape>
            <corners android:radius="2dp" />
            <solid android:color="@color/ques_bt_bg" />
            <stroke android:width="1px" android:color="@color/ques_bt_text_color_light" />
        </shape>
    </item>
</selector>


就拿BlogFragment.java来说综合->博客页面数据的加载过程

重写了基类onCreateView()的initWidget()在调用了super.initWidget()方法初始化了父类中ListView、SuperRefreshLayout、EmptyLayout之后就开始初始化顶部Tab标签的GridView

接着执行initData(),调用了super.initData()在父类的initData()中初始化了从子类获取到的适配器对象设置给ListView,初始化网络回调处理的对象,然后开启线程加载数据,首先读取缓存数据如果有缓存数据就显示缓存数据然后执行onRefreshing(),否则设置其他条件后开始执行onRefreshing(), 在onRefreshing()方法中先设置刷新标识位,然后调用子类重写的requestData()方法请求数据,携带父类中初始化过的网络回调处理的对象

    protected void setListData(ResultBean<PageBean<T>> resultBean) {
        //is refresh
        mBean.setNextPageToken(resultBean.getResult().getNextPageToken());
        if (mIsRefresh) { // 首次加载或者下拉刷新执行逻辑
            // cache the time
            mTime = resultBean.getTime();
            mBean.setItems(resultBean.getResult().getItems());
            mAdapter.clear();
            mAdapter.addItem(mBean.getItems());
            mBean.setPrevPageToken(resultBean.getResult().getPrevPageToken());
            /**
             * 默认加载更多由多个条件决定,mCanLoadMore默认为false,外部可控制,如果有数据则置为true,否则不具备加载更多功能
             * 如果在有数据的情况下,断掉网络,上拉到底部会显示"网络错误",如果再链接网络继续上拉会更新底部布局控件显示
             **/
            mRefreshLayout.setCanLoadMore();
            AppOperator.runOnThread(new Runnable() {
                @Override
                public void run() {
                    CacheManager.saveObject(getActivity(), mBean, CACHE_NAME);
                }
            });
        } else { // 加载更多逻辑
            mAdapter.addItem(resultBean.getResult().getItems());
        }
        if (resultBean.getResult().getItems().size() < 20) {
            setFooterType(TYPE_NO_MORE);
            // mRefreshLayout.setNoMoreData();
        }
        if (mAdapter.getDatas().size() > 0) {
            mErrorLayout.setErrorType(EmptyLayout.HIDE_LAYOUT);
            mRefreshLayout.setVisibility(View.VISIBLE);
        } else {
            mErrorLayout.setErrorType(EmptyLayout.NODATA);
        }
    }

ItemView开始的一个图标的实现

        String text = "";
        SpannableStringBuilder spannable = new SpannableStringBuilder(text);
        if (item.isOriginal()) {
            spannable.append("[icon] ");
            Drawable originate = mCallback.getContext().getResources().getDrawable(R.mipmap.ic_label_originate);
            if (originate != null) {
                originate.setBounds(0, 0, originate.getIntrinsicWidth(), originate.getIntrinsicHeight());
            }
            ImageSpan imageSpan = new ImageSpan(originate, ImageSpan.ALIGN_BOTTOM);
            spannable.setSpan(imageSpan, 0, 6, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        }


        if (item.isRecommend()) {
            spannable.append("[icon] ");
            Drawable recommend = mCallback.getContext().getResources().getDrawable(R.mipmap.ic_label_recommend);
            if (recommend != null) {
                recommend.setBounds(0, 0, recommend.getIntrinsicWidth(), recommend.getIntrinsicHeight());
            }
            ImageSpan imageSpan = new ImageSpan(recommend, ImageSpan.ALIGN_BOTTOM);
            if (item.isOriginal()) {
                spannable.setSpan(imageSpan, 7, 13, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
            } else {
                spannable.setSpan(imageSpan, 0, 6, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
            }
        }
        title.setText(spannable.append(item.getTitle()));


动弹Tab

TweetFragment.java


图片点击事件的监听抽象类,可以实现传递其他参数

private abstract class OnTweetImageClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            onClick(v, Integer.parseInt(v.getTag(R.id.iv_tweet_face).toString()),
                    Integer.parseInt(v.getTag(R.id.iv_tweet_image).toString()));
        }


        public abstract void onClick(View v, int position, int imagePosition);
    }

    可能有多个图片的布局容器使用的是自定义控件的FlowLayout

        FlowLayout flowLayout = vh.getView(R.id.fl_image);
        flowLayout.removeAllViews();
        if (images != null && images.length > 0) {
            flowLayout.setVisibility(View.VISIBLE);
            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams((int) Ui.dipToPx(mCallback.getContext().getResources(), 64)
                    , (int) Ui.dipToPx(mCallback.getContext().getResources(), 64));
            for (int i = 0; i < images.length; i++) {
                ImageView imageView = new ImageView(mCallback.getContext());
                imageView.setLayoutParams(layoutParams);
                imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
                imageView.setTag(R.id.iv_tweet_image, i);
                imageView.setOnClickListener(imageClickListener);

                String path = images[i].getThumb();
                DrawableRequestBuilder builder = getImgLoader().load(path)
                        .centerCrop()
                        .placeholder(R.color.grey_200)
                        .error(R.mipmap.ic_default_image_error);
                if (path.toLowerCase().endsWith("gif"))
                    builder = builder.diskCacheStrategy(DiskCacheStrategy.SOURCE);
                builder.into(imageView);
                flowLayout.addView(imageView);
            }
        } else {
            flowLayout.setVisibility(View.GONE);
        }

具体细节查看TweetAdapter.java文件,图片预览细节ImageGalleryActivity.java文件

    /**
     * 以友好的方式显示时间
     *
     * @param sdate
     * @return
     */
    public static String friendly_time(String sdate) {
        Date time = null;


        if (TimeZoneUtil.isInEasternEightZones())
            time = toDate(sdate);
        else
            time = TimeZoneUtil.transformTime(toDate(sdate),
                    TimeZone.getTimeZone("GMT+08"), TimeZone.getDefault());


        if (time == null) {
            return "Unknown";
        }
        String ftime = "";
        Calendar cal = Calendar.getInstance();


        // 判断是否是同一天
        String curDate = dateFormater2.get().format(cal.getTime());
        String paramDate = dateFormater2.get().format(time);
        if (curDate.equals(paramDate)) {
            int hour = (int) ((cal.getTimeInMillis() - time.getTime()) / 3600000);
            if (hour == 0)
                ftime = Math.max(
                        (cal.getTimeInMillis() - time.getTime()) / 60000, 1)
                        + "分钟前";
            else
                ftime = hour + "小时前";
            return ftime;
        }


        long lt = time.getTime() / 86400000;
        long ct = cal.getTimeInMillis() / 86400000;
        int days = (int) (ct - lt);
        if (days == 0) {
            int hour = (int) ((cal.getTimeInMillis() - time.getTime()) / 3600000);
            if (hour == 0)
                ftime = Math.max(
                        (cal.getTimeInMillis() - time.getTime()) / 60000, 1)
                        + "分钟前";
            else
                ftime = hour + "小时前";
        } else if (days == 1) {
            ftime = "昨天";
        } else if (days == 2) {
            ftime = "前天 ";
        } else if (days > 2 && days < 31) {
            ftime = days + "天前";
        } else if (days >= 31 && days <= 2 * 31) {
            ftime = "一个月前";
        } else if (days > 2 * 31 && days <= 3 * 31) {
            ftime = "2个月前";
        } else if (days > 3 * 31 && days <= 4 * 31) {
            ftime = "3个月前";
        } else {
            ftime = dateFormater2.get().format(time);
        }
        return ftime;
    }


下拉刷新上拉加载更多控件RecyclerRefreshLayout.java


TabLayout+ViewPager是一个Fragment【TweetDetailViewPagerFragment】填充了详情页的一个帧布局,ViewPager中是2个Fragment,上滑布局后TabLayot的tab会固定在顶部,同样是使用了协调布局的一个属性+自定义的一个类, 底部的发表评论布局是在KeyboardInputDelegation.java类中填充的


public class TweetDetailActivity extends BaseBackActivity implements TweetDetailContract.Operator中initWidget()方法中有如下片段,此处拿到了Framgent的属性赋值给了Activity中的属性了,当用户删除信息后需要更新Fragment中TabLayout中Tab的数据

        TweetDetailViewPagerFragment mPagerFrag = TweetDetailViewPagerFragment.instantiate(this);
        mCmnViewImp = mPagerFrag.getCommentViewHandler();
        mThumbupViewImp = mPagerFrag.getThumbupViewHandler();
        mAgencyViewImp = mPagerFrag.getAgencyViewHandler();
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.fragment_container, mPagerFrag)
                .commit();


public class TweetDetailViewPagerFragment extends Fragment implements TweetDetailContract.ICmnView, TweetDetailContract.IThumbupView, TweetDetailContract.IAgencyView {

private ViewPager mViewPager;

        private TabLayout mTabLayout;
   
    protected FragmentStatePagerAdapter mAdapter;
   
    private TweetDetailContract.ICmnView mCmnViewImp;
   
    private TweetDetailContract.IThumbupView mThumbupViewImp;
   
    private TweetDetailContract.Operator mOperator;

}

mOperator的实例化

    public void onAttach(Activity activity) {
        super.onAttach(activity);
        mOperator = (TweetDetailContract.Operator) activity;
    }


mCmnViewImp

mThumbupViewImp

接口对象的实例化

onViewCreated() {

if (mAdapter == null){
final ListTweetLikeUsersFragment mCmnFrag;
mThumbupViewImp = mCmnFrag = ListTweetLikeUsersFragment.instantiate(mOperator, this);

    final ListTweetCommentFragment mThumbupFrag;
    mCmnViewImp = mThumbupFrag = ListTweetCommentFragment.instantiate(mOperator, this);
}  

}


点赞列表页面

public class ListTweetLikeUsersFragment extends BaseRecyclerViewFragment<TweetLike> implements TweetDetailContract.IThumbupView {   

    private TweetDetailContract.Operator mOperator;
    private TweetDetailContract.IAgencyView mAgencyView; 


    /** 两个接口对象的实例化 */

    public static ListTweetLikeUsersFragment instantiate(TweetDetailContract.Operator operator, TweetDetailContract.IAgencyView mAgencyView) {
        ListTweetLikeUsersFragment fragment = new ListTweetLikeUsersFragment();
        fragment.mOperator = operator;
        fragment.mAgencyView = mAgencyView;
        return fragment;
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        mOperator = (TweetDetailContract.Operator) activity;
    }  

}

在initViewget()方法中

    protected void initWidget(View root) {
        super.initWidget(root);
        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
                    mOperator.onScroll();
                }
            }
        });
    }

调用的就是activity中的onScroll()方法

当请求数据成功的时候机会调用mAgencyView.resetCmnCount(mAdapter.getCount());其实就是回调ViewPager所在类TweetDetailViewPagerFragment的方法更新TabLayout中对应Tab的显示



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值