精华内容
下载资源
问答
  • 朋友圈的设计及实现。

    万次阅读 多人点赞 2017-11-21 15:04:32
    最近项目需求需要模拟微信的朋友圈功能,实现可以发送图文消息,好友可以查看,满足添加新的好友之后,可以在朋友圈刷新到好友的消息,看了许多文章,琢磨出来一套流程。这里只记录一下我的实现思路,并贴上实际...

    需求

    最近项目需求需要模拟微信的朋友圈功能,实现可以发送图文消息,好友可以查看,满足添加新的好友之后,可以在朋友圈中刷新到好友的消息,看了许多文章,琢磨出来一套流程。这里只记录一下我的实现思路,并不贴上实际的代码。

    数据库设计

    核心结构有三张表:

    #消息表
    CREATE TABLE `t_friend_circle_message` (
      `id` bigint(15) NOT NULL AUTO_INCREMENT COMMENT '主键',
      `uid` bigint(15) DEFAULT NULL COMMENT '用户id',
      `content` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
      `picture` varchar(200) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT '' COMMENT '图片',
      `location` varbinary(100) DEFAULT '' COMMENT '位置',
      `create_time` datetime DEFAULT NULL COMMENT '创建日期',
      PRIMARY KEY (`id`)
    )

    消息表很好理解,存储用户发送的内容,图片存地址。
    utf8mb4格式可以存储emoji表情,具体可以参照之前的一篇文章mysql支持emjoy

    #时间轴表
    CREATE TABLE `t_friend_circle_timeline` (
      `id` bigint(15) NOT NULL AUTO_INCREMENT,
      `uid` bigint(15) DEFAULT NULL COMMENT '用户id',
      `fcmid` bigint(15) DEFAULT NULL COMMENT '朋友圈信息id',
      `is_own` int(1) DEFAULT '0' COMMENT '是否是自己的',
      `create_time` datetime DEFAULT NULL COMMENT '创建日期',
      PRIMARY KEY (`id`)
    )
    

    时间轴表在朋友圈中是最关键的,存储着所有用的时间轴信息,因为当用户去拉取好友圈的时候,查询的就是本表,is_own字段用来区分当前数据是自己的发布还是好友发布的消息。

    #评论表
    CREATE TABLE `t_friend_circle_comment` (
      `id` bigint(15) NOT NULL AUTO_INCREMENT,
      `fcmid` bigint(15) DEFAULT NULL COMMENT '朋友圈信息id',
      `uid` bigint(15) DEFAULT NULL COMMENT '用户id',
      `content` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
      `create_time` datetime DEFAULT NULL COMMENT '创建日期',
      `like_count` int(10) DEFAULT '0' COMMENT '点赞数',
      PRIMARY KEY (`id`)
    ) 

    评论表,没有什么好解释的。。
    2018年6月8日15:28:21:like_count字段在这里并没有实现功能,参考点赞功能实现

    好友圈逻辑

    发布朋友圈消息

    当用户发布一条朋友圈消息的时候,后端逻辑的处理(A和B已经是好友关系):

    1. 用户A在朋友圈中发布一条消息,消息表t_friend_circle_message写入一条数据。
    2. 时间轴表t_friend_circle_timeline中增加一条数据,uid设置A,is_own设置为1,表示在A的时间轴中增加一条自己发布的消息。
    3. 查询用户A的好友,查到用户B(如果有还有其他好友D、E等等同样处理)
    4. 时间轴表t_friend_circle_timeline中增加一条数据,uid设置B,is_own设置为0,表示在B的时间轴中增加一条好友发布的消息。
    添加好友

    当用户A,添加用户C为好友之后,触发同步好友时间轴的操作

      INSERT INTO t_friend_circle_timeline (uid,fcmid,is_own,create_time)
                SELECT #{uid},`id`,0,create_time FROM t_friend_circle_message WHERE uid = #{fid};
    • 消息表t_friend_circle_message好友C发布的所有消息添加到自己的时间轴中。
    • 再把消息表t_friend_circle_message自己发布的消息添加到好友C的时间轴中。
    • 使用好is_own字段,因为都是互相添加好友的消息到自己的时间轴中,所以都应该为false(0)。

    总结

    思路参照了微信朋友圈是怎么做的架构?
    做完之后发现这个应该更像QQ空间的心情功能,因为没有所谓的不是好友不可见评论功能,如果谁有这一块的更好的思路请留言^_^。

    展开全文
  • 上一篇博客Android 微信刷新&自带下拉刷新SwipeRefreshLayout(一)我们简单介绍了一下SwipeRefreshLayout的原理,以及对它对了一个简单的拆分,这一节我们就来改改它的代码,实现一个微信朋友圈的下拉刷新效果:...

    上一篇博客Android 微信刷新&自带下拉刷新SwipeRefreshLayout(一)我们简单介绍了一下SwipeRefreshLayout的原理,以及对它对了一个简单的拆分,这一节我们就来改改它的代码,实现一个微信朋友圈的下拉刷新效果:

    这里写图片描述

    我们先看一下系统自带的下拉刷新效果:

    这里写图片描述

    下拉刷新的时候,可以看到一个进度条view,这个进度条view上一篇博客有抽取出来的,不懂的小伙伴可以去看看哈。

    下面说一下仿微信下拉刷新的思路:

    1、修改进度条view为我们微信的图片,然后给个动画让图片旋转

    2、修改微信图片出现的位置为左上角

    3、当下拉的时候,header图片跟着下拉

    好啦!明确了我们的目的之后,我们就开始修改代码了。

    首先替换掉默认的加载进度条为微信的图片
    我们看到MaterialProgressDrawable这个类,这个就是进度条view(上一节也有提到),我们仿照着自己写一个叫WechatProgressDrawable的,代码比较简单,我就直接上代码了:

    package com.yasin.eledemo.swipe;
    
    import android.content.Context;
    import android.content.res.Resources;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.Canvas;
    import android.graphics.ColorFilter;
    import android.graphics.PixelFormat;
    import android.graphics.Rect;
    import android.graphics.drawable.Animatable;
    import android.graphics.drawable.Drawable;
    import android.util.DisplayMetrics;
    import android.view.View;
    import android.view.animation.Animation;
    import android.view.animation.LinearInterpolator;
    import android.view.animation.Transformation;
    
    import com.yasin.eledemo.R;
    
    import java.util.ArrayList;
    
    /**
     * Created by leo on 17/5/14.
     */
    
    public class WechatProgressDrawable extends Drawable implements Animatable{
        private Resources mResources;
        private View mParent;
        /** The list of animators operating on this drawable. */
        private final ArrayList<Animation> mAnimators = new ArrayList<Animation>();
    
    
        // Maps to ProgressBar.Large style
        static final int LARGE = 0;
        // Maps to ProgressBar default style
        static final int DEFAULT = 1;
        static final float LARGE_SIZE=56;
        static final float DEFAULT_SIZE=40;
        static final long ANIMATION_DURATION=3000;
        private float mWidth,mHeight;
        private Animation mAnimation;
        private float mRotation;
        private Bitmap mBitmap;
        public WechatProgressDrawable(Context context, View parent) {
            mParent = parent;
            mResources = context.getResources();
            updateSizes(DEFAULT);
            setupAnimators();
            mBitmap= BitmapFactory.decodeResource(mResources, R.mipmap.icon_wechat1);
            mBitmap=Bitmap.createScaledBitmap(mBitmap,(int)mWidth,(int)mHeight,true);
        }
        public void updateSizes( int size) {
            if (size == LARGE) {
                setSizeParameters(LARGE_SIZE, LARGE_SIZE);
            } else {
                setSizeParameters(DEFAULT_SIZE, DEFAULT_SIZE);
            }
        }
    
        private void setSizeParameters(float width, float height) {
            final DisplayMetrics metrics = mResources.getDisplayMetrics();
            final float screenDensity = metrics.density;
    
            mWidth = width * screenDensity;
            mHeight = height * screenDensity;
        }
        private void setupAnimators() {
            final Animation animation = new Animation() {
                @Override
                protected void applyTransformation(float interpolatedTime, Transformation t) {
                    float rotation=interpolatedTime*1.0f*360;
                    setProgressRotation(rotation);
                }
            };
            animation.setRepeatCount(Animation.INFINITE);
            animation.setRepeatMode(Animation.RESTART);
            animation.setInterpolator(new LinearInterpolator());
            animation.setAnimationListener(new Animation.AnimationListener() {
    
                @Override
                public void onAnimationStart(Animation animation) {
                }
    
                @Override
                public void onAnimationEnd(Animation animation) {
                    // do nothing
                }
    
                @Override
                public void onAnimationRepeat(Animation animation) {
    
                }
            });
            mAnimation = animation;
        }
        @Override
        public void draw(Canvas c) {
            final Rect bounds = getBounds();
            final int saveCount = c.save();
            c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY());
            c.drawBitmap(mBitmap,0,0,null);
            c.restoreToCount(saveCount);
        }
        /**
         * Set the amount of rotation to apply to the progress spinner.
         *
         * @param rotation Rotation is from [0..1]
         */
        public void setProgressRotation(float rotation) {
            this.mRotation=rotation;
            invalidateSelf();
        }
        @Override
        public int getIntrinsicHeight() {
            return (int) mHeight;
        }
    
        @Override
        public int getIntrinsicWidth() {
            return (int) mWidth;
        }
        @Override
        public void setAlpha(int alpha) {
        }
        public int getAlpha() {
            return 255;
        }
        @Override
        public void setColorFilter(ColorFilter colorFilter) {
        }
        @Override
        public int getOpacity() {
            return PixelFormat.TRANSLUCENT;
        }
    
        @Override
        public void start() {
            mAnimation.reset();
            mAnimation.setDuration(ANIMATION_DURATION);
            mParent.startAnimation(mAnimation);
        }
    
        @Override
        public void stop() {
            mParent.clearAnimation();
            setProgressRotation(0);
        }
    
        @Override
        public boolean isRunning() {
            final ArrayList<Animation> animators = mAnimators;
            final int N = animators.size();
            for (int i = 0; i < N; i++) {
                final Animation animator = animators.get(i);
                if (animator.hasStarted() && !animator.hasEnded()) {
                    return true;
                }
            }
            return false;
        }
    }
    

    我们来测试一下:

     WechatProgressDrawable drawable1=new WechatProgressDrawable(this,container);
            drawable1.setAlpha(255);
            mShopCart.setImageDrawable(drawable1);
            drawable1.start();

    这里写图片描述

    卧槽,gif录制出问题了还是咋了,调用了start方法后中间那个图标是可以旋转的。

    好啦!!搞定基础的进度条view后,我们需要把它放进SwipeRefreshLayout中,在SwipeRefreshLayout中我们看到原有的

      int mSpinnerOffsetEnd;
    
        MaterialProgressDrawable mProgress;
    
        private Animation mScaleAnimation;
    

    我们把 MaterialProgressDrawable对象改为

    
        int mSpinnerOffsetEnd;
    
        WechatProgressDrawable mProgress;
    
        private Animation mScaleAnimation;
    

    好啦!可能有些地方的api你得删掉了,比如说原有的:

    mProgress.showArrow(true);

    这些方法你就得删掉了,因为我们的WechatProgressDrawable里面没有这些方法,并且我们也不需要了。

    接下来我们需要把WechatProgressDrawable摆放在左上角的位置,让它可以从左上角出来:

    在SwipeRefreshLayout的onlayout方法中处理了,
    原来是这样的:

     @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            final int width = getMeasuredWidth();
            final int height = getMeasuredHeight();
            if (getChildCount() == 0) {
                return;
            }
            if (mTarget == null) {
                ensureTarget();
            }
            if (mTarget == null) {
                return;
            }
            final View child = mTarget;
            final int childLeft = getPaddingLeft();
            final int childTop = getPaddingTop();
            final int childWidth = width - getPaddingLeft() - getPaddingRight();
            final int childHeight = height - getPaddingTop() - getPaddingBottom();
            child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
            int circleWidth = mCircleView.getMeasuredWidth();
            int circleHeight = mCircleView.getMeasuredHeight();
            mCircleView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop,
                    (width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight);
        }

    改过之后:

    @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            final int width = getMeasuredWidth();
            final int height = getMeasuredHeight();
            if (getChildCount() == 0) {
                return;
            }
            if (mTarget == null) {
                ensureTarget();
            }
            if (mTarget == null) {
                return;
            }
            final View child = mTarget;
            final int childLeft = getPaddingLeft();
            final int childTop = getPaddingTop();
            final int childWidth = width - getPaddingLeft() - getPaddingRight();
            final int childHeight = height - getPaddingTop() - getPaddingBottom();
            child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
            int circleWidth = mCircleView.getMeasuredWidth();
            int circleHeight = mCircleView.getMeasuredHeight();
            mCircleView.layout((100), mCurrentTargetOffsetTop,
                    (100+circleWidth), mCurrentTargetOffsetTop + circleHeight);
        }

    我就简单的设置其在左边100px的位置了。

    好吧~修改完毕后,我们就可以简单的玩起来了~~~我就不测试了哈!!

    为了和微信几乎一样的效果,我们需要加一个header(也就是一个imageview),然后当往下拉的时候,图片也往下拉,手指松开后,图片回到原有的位置。

    其实呢也很简单,

    <?xml version="1.0" encoding="utf-8"?>
    <com.yasin.eledemo.swipe.SwipeRefreshLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/id_swipe"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            >
            <ImageView
                android:id="@+id/id_head_view"
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:src="@mipmap/icon_beaty"
                android:layout_marginTop="-40dp"
                android:scaleType="centerCrop"
                />

    可以看到,我在LinearLayout下面直接放了一个imageview,然后设置其默认样式为 android:layout_marginTop=”-40dp。

    然后我们通过下拉的距离改变android:layout_marginTop来实现我们的图片滑动操作。

    首先我们需要在SwipeRefreshLayout中定义一个view叫:

     private View mHeadView;

    然后我们需要找到我们的 private View mHeadView;并且记录一下原始的margintop值:

    private float originHeaderMargin;
        private void ensureTarget() {
            // Don't bother getting the parent height if the parent hasn't been laid
            // out yet.
            if (mTarget == null) {
                for (int i = 0; i < getChildCount(); i++) {
                    View child = getChildAt(i);
                    if (!child.equals(mCircleView)) {
                        mTarget = child;
                        break;
                    }
                }
            }
            if(mTarget!=null){
                mHeadView=mTarget.findViewById(R.id.id_head_view);
                if(mHeadView!=null&&mHeadView.getLayoutParams() instanceof MarginLayoutParams){
                    MarginLayoutParams lp= (MarginLayoutParams) mHeadView.getLayoutParams();
                    if(originHeaderMargin==0){
                        originHeaderMargin=lp.topMargin;
                        headerBackAni.setDuration(ANIMATE_TO_START_DURATION);
                        headerBackAni.setInterpolator(new AccelerateDecelerateInterpolator());
                    }
                }
            }
        }

    ensureTarget这个方法我上一节有介绍哈~~

    然后当手指滑动的时候,我们需要根据滑动的距离改变margintop值,所以我们进入到SwipeRefreshLayout的onTouchEvent方法中修改下逻辑:

      @Override
    case MotionEvent.ACTION_MOVE: {
                    pointerIndex = ev.findPointerIndex(mActivePointerId);
                    if (pointerIndex < 0) {
                        Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
                        return false;
                    }
    
                    final float y = ev.getY(pointerIndex);
                    startDragging(y);
    
                    if (mIsBeingDragged) {
                        final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
                        if (overscrollTop > 0) {
                            Log.e("TAG", "onTouchEvent:--->"+overscrollTop);
                            moveSpinner(overscrollTop);
                            //开始控制headerview的滑动位置
                            setHeaderMarginOffset(overscrollTop/2);
                        } else {
                            return false;
                        }
                    }
                    break;
        }
     private void setHeaderMarginOffset(float offset) {
            if (mHeadView != null && mHeadView.getLayoutParams() instanceof MarginLayoutParams) {
                MarginLayoutParams lp = (MarginLayoutParams) mHeadView.getLayoutParams();
                lp.topMargin = (int) (originHeaderMargin+offset);
                mHeadView.setLayoutParams(lp);
            }
        }

    然后手指放开的时候,动画回到原来的位置:

    case MotionEvent.ACTION_UP: {
                    pointerIndex = ev.findPointerIndex(mActivePointerId);
                    if (pointerIndex < 0) {
                        Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id.");
                        return false;
                    }
    
                    if (mIsBeingDragged) {
                        final float y = ev.getY(pointerIndex);
                        final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
                        mIsBeingDragged = false;
                        finishSpinner(overscrollTop);
                        //header动画回到原来的位置
                        startHeaderBackAni();
                        mProgress.stop();
                    }
                    mActivePointerId = INVALID_POINTER;
                    return false;
                }
        }
        private void startHeaderBackAni(){
            mHeadView.clearAnimation();
            mHeadView.startAnimation(headerBackAni);
        }
     private final Animation headerBackAni=new Animation() {
            @Override
            protected void applyTransformation(float interpolatedTime, Transformation t) {
                if (mHeadView != null && mHeadView.getLayoutParams() instanceof MarginLayoutParams) {
                    MarginLayoutParams lp = (MarginLayoutParams) mHeadView.getLayoutParams();
                    float startMargin=lp.topMargin;
                    float endMargin= originHeaderMargin;
                    float margin=startMargin+(endMargin-startMargin)*interpolatedTime;
                    lp.topMargin= (int) margin;
                    mHeadView.setLayoutParams(lp);
                }
            }
        };

    好啦!!我们的改造就撸完了~~

    附上项目的github链接:
    https://github.com/913453448/EleDemo

    欢迎入群~~~~

    展开全文
  • 之前一直有朋友问我点赞怎么实现?今天趁着休息时间整理出来,其实点赞的功能和用户评论差不多,都是显示一个用户列表,只不过评论有评论内容和回复评论功能。实现点赞的思路如下: 1.当用户点击点赞按钮后,根据...

    之前一直有朋友问我点赞怎么实现?今天趁着休息时间整理出来,其实点赞的功能和用户评论差不多,都是显示一个用户列表,只不过评论有评论内容和回复评论功能。实现点赞的思路如下:

    1.当用户点击点赞按钮后,根据点赞状态来判断和显示:

    (1)如果此用户的操作是点赞,就把此用户加入到点赞列表中,并且点赞状态设置为1,然后请求点赞接口,刷新本地数据;

    (2)如果此用户的操作是取消点赞,此时循环查询点赞列表,如果此用户在点赞列表中就把此用户从列表中移除,如果不在点赞列表中就把此用户加入点赞列表,并且把点赞状态设置为0,请求点赞接口,刷新本地数据.

    2.实现以上逻辑的主要代码截图如下:

    /**
     * 请求点赞接口,这里写的模拟数据
     */
    private void getLikeData() {
        if (likeBean != null ) {
            List<CircleBean.DataBean> list = circleAdapter.getData();
            if (isLike == 1) {
                Iterator it = list.get(comPosition).getLike_list().iterator();
                List<LikeListBean> likeListBeans = new ArrayList<>();
                while (it.hasNext()) {
                    LikeListBean info = (LikeListBean) it.next();
                    if (info.getUser_id().equals(String.valueOf(userId))) {
                        it.remove();
                    } else {
                        likeListBeans.add(info);
                    }
                }
                LikeListBean likeListBean = new LikeListBean();
                likeListBean.setUser_id(userId+"");
                likeListBean.setUser_name(userName);
                list.get(comPosition).setIs_like(0);
                list.get(comPosition).setLike_list(likeListBeans);
                ToastUtils.ToastShort("取消点赞");
            } else {
                LikeListBean likeListBean = new LikeListBean();
                likeListBean.setUser_id(userId + "");
                likeListBean.setUser_name(userName);
                list.get(comPosition).getLike_list().add(likeListBean);
                list.get(comPosition).setIs_like(1);
                ToastUtils.ToastShort("点赞成功");
            }
            circleAdapter.notifyDataSetChanged();
        }
    }

    3.实现的效果截图如下:由于是写的模拟数据,所以效果和真实的差不多,大家只要理解逻辑就行.

    4.MainActivity完整代码如下:

    package com.example.expandtextview;
    
    import android.Manifest;
    import android.annotation.SuppressLint;
    import android.app.Activity;
    import android.content.ContentValues;
    import android.content.Context;
    import android.content.Intent;
    import android.graphics.Bitmap;
    import android.graphics.Rect;
    import android.net.Uri;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.provider.MediaStore;
    import android.text.Editable;
    import android.text.TextUtils;
    import android.text.TextWatcher;
    import android.util.Log;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewTreeObserver;
    import android.widget.EditText;
    import android.widget.ImageView;
    import android.widget.LinearLayout;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import androidx.annotation.Nullable;
    import androidx.appcompat.app.AlertDialog;
    import androidx.appcompat.app.AppCompatActivity;
    import androidx.constraintlayout.widget.ConstraintLayout;
    import androidx.recyclerview.widget.RecyclerView;
    
    import com.bumptech.glide.Glide;
    import com.bumptech.glide.load.DataSource;
    import com.bumptech.glide.load.engine.GlideException;
    import com.bumptech.glide.request.RequestListener;
    import com.bumptech.glide.request.target.Target;
    import com.example.expandtextview.activity.PlayVideoActivity;
    import com.example.expandtextview.activity.WatchLiveActivity;
    import com.example.expandtextview.adapter.CircleAdapter;
    import com.example.expandtextview.bean.CircleBean;
    import com.example.expandtextview.bean.CommentListBean;
    import com.example.expandtextview.bean.LikeBean;
    import com.example.expandtextview.bean.LikeListBean;
    import com.example.expandtextview.bean.WeatherEvent;
    import com.example.expandtextview.util.AssetsUtil;
    import com.example.expandtextview.util.CommonUtils;
    import com.example.expandtextview.util.Constants;
    import com.example.expandtextview.util.GlideSimpleTarget;
    import com.example.expandtextview.util.KeyboardUtil;
    import com.example.expandtextview.util.RxBus;
    import com.example.expandtextview.util.StorageUtil;
    import com.example.expandtextview.util.ToastUtils;
    import com.example.expandtextview.util.Utils;
    import com.example.expandtextview.view.LikePopupWindow;
    import com.example.expandtextview.view.OnPraiseOrCommentClickListener;
    import com.example.expandtextview.view.ScrollSpeedLinearLayoutManger;
    import com.example.expandtextview.view.SpaceDecoration;
    import com.github.ielse.imagewatcher.ImageWatcher;
    import com.tbruyelle.rxpermissions2.RxPermissions;
    
    import java.lang.ref.WeakReference;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Objects;
    
    import io.reactivex.Observer;
    import io.reactivex.disposables.CompositeDisposable;
    import io.reactivex.disposables.Disposable;
    
    /**
     * @作者: njb
     * @时间: 2019/7/22 10:53
     * @描述: 仿微信朋友圈实现
     */
    public class MainActivity extends AppCompatActivity implements View.OnClickListener, CircleAdapter.Click, ImageWatcher.OnPictureLongPressListener, ImageWatcher.Loader {
        private RecyclerView recyclerView;
        private CircleAdapter circleAdapter;
        private String content;
        private LikePopupWindow likePopupWindow;
        private EditText etComment;
        private LinearLayout llComment;
        private TextView tvSend;
        private LinearLayout llScroll;
        private int editTextBodyHeight;
        private int currentKeyboardH;
        private int selectCommentItemOffset;
        private int commentPosition;
        protected final String TAG = this.getClass().getSimpleName();
        CompositeDisposable compositeDisposable;
        private ScrollSpeedLinearLayoutManger layoutManger;
        private List<CircleBean.DataBean> dataBeans;
        ImageWatcher imageWatcher;
        private int isLike;
        private int comPosition;
        private String to_user_id;
        private String to_user_name;
        private String circle_id;
        private String userId;
        private String userName;
        private RxPermissions rxPermissions;
        private static MyHandler myHandler;
        private static String dstPath;
        private ImageView ivBg;
        private ImageView ivUser;
        private TextView tvName;
        private ConstraintLayout clMessage;
        private ImageView ivMessage;
        private TextView tvMessage;
        private List<LikeListBean> likeBean;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initViews();
            initData();
            initAdapter();
            setListener();
            initRxBus();
        }
    
        private void initRxBus() {
            compositeDisposable = new CompositeDisposable();
            RxBus.getInstance().toObservable(WeatherEvent.class)
                    .subscribe(new Observer<WeatherEvent>() {
                        @Override
                        public void onSubscribe(Disposable d) {
                            compositeDisposable.add(d);
                        }
    
                        @Override
                        public void onNext(WeatherEvent weatherEvent) {
                            Log.e("weather", weatherEvent.getTemperature() + "-**-" + weatherEvent.getCityName());
                        }
    
                        @Override
                        public void onError(Throwable e) {
    
                        }
    
                        @Override
                        public void onComplete() {
    
                        }
                    });
        }
    
        private void setListener() {
            tvSend.setOnClickListener(this);
            etComment.addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
    
                }
    
                @Override
                public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                    content = etComment.getText().toString();
                    if (etComment.getText().length() == 500) {
                        Toast.makeText(MainActivity.this, getResources().getString(R.string.the_content_of_the_comment_cannot_exceed_500_words), Toast.LENGTH_SHORT).show();
                    }
                }
    
                @Override
                public void afterTextChanged(Editable editable) {
                }
            });
            recyclerView.setOnTouchListener((view, motionEvent) -> {
                if (llComment.getVisibility() == View.VISIBLE) {
                    updateEditTextBodyVisible(View.GONE);
                    return true;
                }
                return false;
            });
            circleAdapter.setOnItemChildClickListener((adapter, view, position) -> {
                userId = Objects.requireNonNull(circleAdapter.getItem(position)).getId();
                userName = Objects.requireNonNull(circleAdapter.getItem(position)).getUser_name();
                isLike = Objects.requireNonNull(circleAdapter.getItem(position)).getIs_like();
                likeBean = new ArrayList<>(0);
                likeBean  = circleAdapter.getData().get(position).getLike_list();
                comPosition = position;
                switch (view.getId()) {
                    case R.id.iv_edit:
                        //评论弹框
                        showLikePopupWindow(view, position);
                        break;
                    case R.id.tv_delete:
                        //删除朋友圈
                        deleteCircleDialog();
                        break;
                        //点击视频按钮跳转到视频播放界面
                    case R.id.video_view:
                        Intent intent = new Intent(this, PlayVideoActivity.class);
                        intent.putExtra("url", Objects.requireNonNull(circleAdapter.getItem(position)).getVideo());
                        startActivity(intent);
                        break;
                    default:
                        break;
                }
            });
        }
    
        private void showLikePopupWindow(View view, int position) {
            //item 底部y坐标
            final int mBottomY = getCoordinateY(view) + view.getHeight();
            if (likePopupWindow == null) {
                likePopupWindow = new LikePopupWindow(this, isLike);
            }
            likePopupWindow.setOnPraiseOrCommentClickListener(new OnPraiseOrCommentClickListener() {
                @Override
                public void onPraiseClick(int position) {
                    //调用点赞接口
                    getLikeData();
                    likePopupWindow.dismiss();
                }
    
                @Override
                public void onCommentClick(int position) {
                    llComment.setVisibility(View.VISIBLE);
                    etComment.requestFocus();
                    etComment.setHint("说点什么");
                    to_user_id = null;
                    KeyboardUtil.showSoftInput(MainActivity.this);
                    likePopupWindow.dismiss();
                    etComment.setText("");
                    view.postDelayed(() -> {
                        int y = getCoordinateY(llComment) - 20;
                        //评论时滑动到对应item底部和输入框顶部对齐
                        recyclerView.smoothScrollBy(0, mBottomY - y);
                    }, 300);
                }
    
                @Override
                public void onClickFrendCircleTopBg() {
    
                }
    
                @Override
                public void onDeleteItem(String id, int position) {
    
                }
    
            }).setTextView(isLike).setCurrentPosition(position);
            if (likePopupWindow.isShowing()) {
                likePopupWindow.dismiss();
            } else {
                likePopupWindow.showPopupWindow(view);
            }
        }
    
        /**
         * 请求点赞接口,这里写的模拟数据
         */
        private void getLikeData() {
            if (likeBean != null ) {
                List<CircleBean.DataBean> list = circleAdapter.getData();
                if (isLike == 1) {
                    Iterator it = list.get(comPosition).getLike_list().iterator();
                    List<LikeListBean> likeListBeans = new ArrayList<>();
                    while (it.hasNext()) {
                        LikeListBean info = (LikeListBean) it.next();
                        if (info.getUser_id().equals(String.valueOf(userId))) {
                            it.remove();
                        } else {
                            likeListBeans.add(info);
                        }
                    }
                    LikeListBean likeListBean = new LikeListBean();
                    likeListBean.setUser_id(userId+"");
                    likeListBean.setUser_name(userName);
                    list.get(comPosition).setIs_like(0);
                    list.get(comPosition).setLike_list(likeListBeans);
                    ToastUtils.ToastShort("取消点赞");
                } else {
                    LikeListBean likeListBean = new LikeListBean();
                    likeListBean.setUser_id(userId + "");
                    likeListBean.setUser_name(userName);
                    list.get(comPosition).getLike_list().add(likeListBean);
                    list.get(comPosition).setIs_like(1);
                    ToastUtils.ToastShort("点赞成功");
                }
                circleAdapter.notifyDataSetChanged();
            }
        }
    
    
        private void deleteCircleDialog() {
            final AlertDialog.Builder alert = new AlertDialog.Builder(this);
            alert.setTitle("提示");
            alert.setMessage("你确定要删除吗?");
            alert.setNegativeButton("取消", null);
            alert.setPositiveButton("确定", (dialog, which) -> {
                //调接口删除,这里写死
                dialog.dismiss();
                startActivity(new Intent(this, WatchLiveActivity.class));
            });
            alert.show();
        }
    
    
        private void setViewTreeObserver() {
            final ViewTreeObserver swipeRefreshLayoutVTO = llScroll.getViewTreeObserver();
            swipeRefreshLayoutVTO.addOnGlobalLayoutListener(() -> {
                Rect r = new Rect();
                llScroll.getWindowVisibleDisplayFrame(r);
                int statusBarH = Utils.getStatusBarHeight();//状态栏高度
                int screenH = llScroll.getRootView().getHeight();
                if (r.top != statusBarH) {
                    //r.top代表的是状态栏高度,在沉浸式状态栏时r.top=0,通过getStatusBarHeight获取状态栏高度
                    r.top = statusBarH;
                }
                int keyboardH = screenH - (r.bottom - r.top);
                Log.d(TAG, "screenH= " + screenH + " &keyboardH = " + keyboardH + " &r.bottom=" + r.bottom + " &top=" + r.top + " &statusBarH=" + statusBarH);
    
                if (keyboardH == currentKeyboardH) {//有变化时才处理,否则会陷入死循环
                    return;
                }
                currentKeyboardH = keyboardH;
                editTextBodyHeight = llComment.getHeight();
                if (keyboardH < 150) {//说明是隐藏键盘的情况
                    MainActivity.this.updateEditTextBodyVisible(View.GONE);
                    return;
                }
            });
        }
    
        /**
         * 初始化控件
         */
        private void initViews() {
            recyclerView = findViewById(R.id.recyclerView);
            llComment = findViewById(R.id.ll_comment);
            etComment = findViewById(R.id.et_comment);
            tvSend = findViewById(R.id.tv_send_comment);
            llScroll = findViewById(R.id.ll_scroll);
            imageWatcher = findViewById(R.id.imageWatcher);
            //初始化仿微信图片滑动加载器
            imageWatcher.setTranslucentStatus(Utils.calcStatusBarHeight(this));
            imageWatcher.setErrorImageRes(R.mipmap.error_picture);
            imageWatcher.setOnPictureLongPressListener(this);
            imageWatcher.setLoader(this);
            getPermissions();
            myHandler = new MyHandler(this);
        }
    
        /**
         * 初始化数据
         *
         * @param
         */
        private void initData() {
            dataBeans = new ArrayList<>();
            dataBeans = AssetsUtil.getStates(this);
        }
    
        /**
         * 设置adapter
         */
        private void initAdapter() {
            View headCircle = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_circle_head, null);
            initHead(headCircle);
            circleAdapter = new CircleAdapter(dataBeans, imageWatcher, llComment, etComment, this);
            layoutManger = new ScrollSpeedLinearLayoutManger(this);
            recyclerView.setLayoutManager(layoutManger);
            layoutManger.setSpeedSlow();
            circleAdapter.addHeaderView(headCircle);
            recyclerView.addItemDecoration(new SpaceDecoration(this));
            recyclerView.setAdapter(circleAdapter);
        }
    
        /**
         * 初始化消息提醒布局
         * @param headCircle
         */
        private void initHead(View headCircle) {
            ivBg = headCircle.findViewById(R.id.iv_bg);
            ivUser = headCircle.findViewById(R.id.iv_user);
            tvName = headCircle.findViewById(R.id.tv_name);
            clMessage = headCircle.findViewById(R.id.cl_message);
            ivMessage = headCircle.findViewById(R.id.message_avatar);
            tvMessage = headCircle.findViewById(R.id.message_detail);
            ivBg.setOnClickListener(this);
            ivUser.setOnClickListener(this);
            //这里是调用接口数据,本例子写的假数据,可以自己根据需求来写
            clMessage.setVisibility(View.VISIBLE);
            tvMessage.setText("10条新信息");
        }
    
        public void updateEditTextBodyVisible(int visibility) {
            llComment.setVisibility(visibility);
            if (View.VISIBLE == visibility) {
                llComment.requestFocus();
                //弹出键盘
                CommonUtils.showSoftInput(etComment.getContext(), etComment);
    
            } else if (View.GONE == visibility) {
                //隐藏键盘
                CommonUtils.hideSoftInput(etComment.getContext(), etComment);
            }
        }
    
        /**
         * 获取控件左上顶点Y坐标
         *
         * @param view
         * @return
         */
        private int getCoordinateY(View view) {
            int[] coordinate = new int[2];
            view.getLocationOnScreen(coordinate);
            return coordinate[1];
        }
    
    
        @Override
        public void onClick(View view) {
            int i = view.getId();
            if (i == R.id.tv_send_comment) {
                if (TextUtils.isEmpty(etComment.getText().toString())) {
                    Toast.makeText(MainActivity.this, "请输入评论内容", Toast.LENGTH_SHORT).show();
                    return;
                }
                //请求接口,在成功回调方法拼接评论信息,这里写死
                getComment();
                setViewTreeObserver();
            }
        }
    
        private void getComment() {
            List<CircleBean.DataBean> list = circleAdapter.getData();
            CommentListBean commentListBean = new CommentListBean();
            //userId为当前用户id,这里只是一个例子所以没有登录注册
            commentListBean.setUser_id(userId);
            //userName为当前用户名称
            commentListBean.setUser_name(userName);
            commentListBean.setTo_user_name(TextUtils.isEmpty(to_user_name) ? "" : to_user_name);
            commentListBean.setTo_user_id(TextUtils.isEmpty(to_user_id) ? "" : to_user_id);
            commentListBean.setCircle_id(circle_id);
            commentListBean.setContent(content);
            if (TextUtils.isEmpty(to_user_id)) {
                ToastUtils.ToastShort("评论成功");
                list.get(comPosition).getComments_list().add(commentListBean);
            } else {
                ToastUtils.ToastShort("回复成功");
                list.get(commentPosition).getComments_list().add(commentListBean);
            }
            circleAdapter.notifyDataSetChanged();
            KeyboardUtil.hideSoftInput(MainActivity.this);
            llComment.setVisibility(View.GONE);
            etComment.setText("");
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            //取消订阅
            RxBus.rxBusUnbund(compositeDisposable);
            myHandler.removeCallbacksAndMessages(null);
        }
    
        @Override
        public void Commend(int position, CommentListBean bean) {
            circle_id = bean.getCircle_id();
            commentPosition = position;
            to_user_name = bean.getUser_name();
            to_user_id = bean.getUser_id();
        }
    
        @Override
        public void load(Context context, Uri uri, ImageWatcher.LoadCallback loadCallback) {
            Glide.with(context).asBitmap().load(uri.toString()).into(new GlideSimpleTarget(loadCallback));
        }
    
        @Override
        public void onPictureLongPress(ImageView v, Uri uri, int pos) {
            final AlertDialog.Builder alert = new AlertDialog.Builder(this);
            alert.setTitle("保存图片");
            alert.setMessage("你确定要保存图片吗?");
            alert.setNegativeButton("取消", null);
            alert.setPositiveButton("确定", (dialog, which) -> {
                rxPermissions
                        .request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        .subscribe(granted -> {
                            if (granted) {
                                if (uri != null) {// Always true pre-M
                                    savePhoto(uri);
                                }
                            } else {
                                ToastUtils.ToastShort("缺少必要权限,请授予权限");
                            }
                        });
                dialog.dismiss();
    
            });
            alert.show();
        }
    
        @SuppressLint("HandlerLeak")
        private class MyHandler extends Handler {
            private WeakReference<Activity> mActivity;
            private Bitmap bitmap;
    
            private MyHandler(Activity activity) {
                mActivity = new WeakReference<>(activity);
            }
    
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                final Activity activity = mActivity.get();
                if (activity != null) {
                    if (msg.what == 1) {
                        try {
                            bitmap = (Bitmap) msg.obj;
                            if (Utils.saveBitmap(bitmap, dstPath, false)) {
                                try {
                                    ContentValues values = new ContentValues(2);
                                    values.put(MediaStore.Images.Media.MIME_TYPE, Constants.MIME_JPEG);
                                    values.put(MediaStore.Images.Media.DATA, dstPath);
                                    getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
                                    ToastUtils.ToastShort(activity, getResources().getString(R.string.picture_save_to));
                                } catch (Exception e) {
                                    ToastUtils.ToastShort(activity, getResources().getString(R.string.picture_save_fail));
                                }
                            } else {
                                ToastUtils.ToastShort(activity, getResources().getString(R.string.picture_save_fail));
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                            ToastUtils.ToastShort(activity, getResources().getString(R.string.picture_save_fail));
                        }
                    }
                }
            }
        }
    
        private void getPermissions() {
            rxPermissions = new RxPermissions(this);
        }
    
        /**
         * 长按保存图片
         * @param uri 图片url地址
         */
        private void savePhoto(Uri uri) {
            Glide.with(MainActivity.this).asBitmap().load(uri).listener(new RequestListener<Bitmap>() {
                @Override
                public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
                    String picPath = StorageUtil.getSystemImagePath();
                    dstPath = picPath + (System.currentTimeMillis() / 1000) + ".jpeg";
                    Message message = Message.obtain();
                    message.what = 1;
                    message.obj = resource;
                    myHandler.sendMessage(message);
                    return false;
                }
    
                @Override
                public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) {
                    return false;
                }
            }).submit();
        }
    }
    

    5.以上就是实现仿朋友圈点赞的简单实现,大家理解逻辑后可以自行尝试,如有问题及时提示,我一定会吸取好的建议,多多学习,一起成长。

    项目的完整源码地址:https://gitee.com/jackning_admin/ExpandTextView

    展开全文
  • 可能去实时刷新页面进行渲染进行操作。所以我们需要将这些数据存储到我们本地的一个新数组当中。 当首次渲染的时候,我们设定一个本地数组来存接口渲染出来的数据。当我们进行操作的时候,再将需要操作的数据push...

    1、需求:
    在这里插入图片描述

    一个动态发布软件,可进行点赞评论,取消赞,删除评论。
    2、实现思路:
    首先需要进行实时点赞评论等操作。不可能去实时刷新页面进行渲染进行操作。所以我们需要将这些数据存储到我们本地的一个新数组当中。

    • 当首次渲染的时候,我们设定一个本地数组来存接口渲染出来的数据。当我们进行操作的时候,再将需要操作的数据push到我们自己的数组当中。所以总结来说:点赞取消 评论 删除评论,也就是对数组的增删操作。
     对于所需要操作的模块来说,一定需要将其抽取为单独组件。只有这样,才好对数据进行管理处理。
    

    3、具体实现代码不再贴出,主要是许多细节的处理。
    4、部分代码步骤:

    • 先在本地state中定义一个存放数据的数组。
      在这里插入图片描述
    • 然后进行后台数据存储处理
    • // 这里只是我的业务数据处理
    
      // 评论数据处理
      getCommentList = () => {
        const { item, dynamicsList: { commentContentList } } = this.props;
        const newCommentList = [];
        item.commentList.forEach((item2) => {
          (commentContentList || []).forEach(item3 => {
            if ((item3.commentId === item2.commentId) && item3.commentType === 0) {
              newCommentList.push({
              });
            }
          });
        });
        this.setState({
          initCommentList: newCommentList,
        });
      }
    
    • 发送评论
      这里更新数组用到了一个react插件, 可自行搜索。
      更新本地数组的同时调用相应的接口即可。

    • react-addons-update

    
    import update from 'react-addons-update';
    
        if (isCommentContent) {
          this.setState({
            initCommentList: update(initCommentList, {
              $push: [{
                commentContent,
                receiverName: '',
                senderAccountId: accountId,
                senderName: accountName,
              }]
            }),
            commentInputVisible: false,
            showComment: false,
          })
        }
    
    展开全文
  • OceanBase这几天霸屏朋友圈! 一派是浮夸的宣传,超越Oracle,世界第一,过度解读,全面否定对手,引起了技术圈内人士的反感,因为刷新TPC-C纪录并能说明OceanBase现在就超越了Oracle。 另一派则是并...
  • 在这篇文章看完之后,我决定断掉一些经常刷新的东西,比如朋友圈,来尝试一下不看新闻是否能带来价值。如果刷朋友圈是寻找存在感,那么我们的一生有很多可以追求的东西,以下为全文,我直接帖了出来。这种转载其实是...
  • 事件分发机制

    2017-01-06 13:58:47
    突然想到前一阵做的类似于朋友圈的界面 下拉刷新需要和微信朋友圈的下拉刷新一致 ,由于上拉翻页用的是之前的框架 打算大动就打算自己重写一个RecyclerView把滑动事件传出来再调整view 但是滑动的时候出现了事件...
  • 1.拿到新手机怎么测试? 2.修改了某个人的备注之后,通讯录的排位没有发生改变,如何确定问题所在? ... ... 5.给一个水杯,可以怎么去测试它。 ...14.手机淘宝页面刷新不出来,你觉得是什么问题? 15.对
  • 今天早上到公司打开电脑本地项目,刷新了下就出现了这个报错???? node官方 以为是自己的代理有什么问题了,再不就是测试服务器换地址了?...总结:遇到凡事不要慌,先把手机拿出来拍个朋友圈???? ...
  • 互联网框架演进 三层: MVC架构 四层: 接入层 业务逻辑层 数据访问层 数据层 五层: 接入层 异步提交层 ...再读:用户再从数据库中读出来 如果你写了马上读就会...朋友圈,先在前端设计一条假的,再在几秒后刷新一条真的
  • 本人在做一个评论功能,个人想法是用recyclerview作为每天朋友圈的滚动列表, 而recyclerview中的item包含一个listview作为添加评论和回复的列表,现在问题是,我 往recyclerview中的某一个listView中添加评论,添加...
  • 14.发朋友圈、查看朋友圈的客户端和后台服务端(DataSnap)的示例源码。 15.按钮在ScrollBox上用手指滑动不会触发点击事件。 16.编辑框在ScrollBox上用手指滑动时不会触发输入事件,并已自动处理虚拟键盘显示/隐藏...

空空如也

空空如也

1 2 3
收藏数 51
精华内容 20
关键字:

朋友圈刷新不出来