├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── gradle.xml ├── misc.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── xiaok │ │ └── androidqzone │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── xiaok │ │ │ └── androidqzone │ │ │ ├── MainActivity.java │ │ │ ├── MyApplication.java │ │ │ ├── VideoBrower.java │ │ │ ├── VideoBrowerActivity.java │ │ │ ├── VideoBrowerAdapter.java │ │ │ └── custom │ │ │ └── CircleImageView.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── head_picture.jpg │ │ ├── ic_launcher_background.xml │ │ ├── video_send_picture.xml │ │ └── video_share_send.png │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_video_brower.xml │ │ └── video_brower_item.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── ic_record_play.png │ │ ├── ic_record_stop.png │ │ ├── record_go_back2.png │ │ ├── video_avatar_02.jpg │ │ ├── video_avatar_03.png │ │ ├── video_avatar_04.png │ │ ├── video_share_comment.png │ │ ├── video_share_good.png │ │ ├── video_share_good_blue.png │ │ └── video_share_share.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── xiaok │ └── androidqzone │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | xmlns:android 11 | 12 | ^$ 13 | 14 | 15 | 16 |
17 |
18 | 19 | 20 | 21 | xmlns:.* 22 | 23 | ^$ 24 | 25 | 26 | BY_NAME 27 | 28 |
29 |
30 | 31 | 32 | 33 | .*:id 34 | 35 | http://schemas.android.com/apk/res/android 36 | 37 | 38 | 39 |
40 |
41 | 42 | 43 | 44 | .*:name 45 | 46 | http://schemas.android.com/apk/res/android 47 | 48 | 49 | 50 |
51 |
52 | 53 | 54 | 55 | name 56 | 57 | ^$ 58 | 59 | 60 | 61 |
62 |
63 | 64 | 65 | 66 | style 67 | 68 | ^$ 69 | 70 | 71 | 72 |
73 |
74 | 75 | 76 | 77 | .* 78 | 79 | ^$ 80 | 81 | 82 | BY_NAME 83 | 84 |
85 |
86 | 87 | 88 | 89 | .* 90 | 91 | http://schemas.android.com/apk/res/android 92 | 93 | 94 | ANDROID_ATTRIBUTE_ORDER 95 | 96 |
97 |
98 | 99 | 100 | 101 | .* 102 | 103 | .* 104 | 105 | 106 | BY_NAME 107 | 108 |
109 |
110 |
111 |
112 |
113 |
-------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidQzone 2 | Android下实现QQ空间说说展示效果,包含视频,点赞,评论,分享 3 | 4 | 效果图: 5 | ![在这里插入图片描述](https://github.com/kuaij/unload_resources/blob/master/1234678.gif) 6 | 7 | ## 使用步骤 8 | 1. 下载并解压项目 9 | 2. 添加视频资源文件,并修改VideoBrowerAcitvity中第43行videoPaths中的路径(使用本地路径,请自行添加视频文件) 10 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion "29.0.2" 6 | defaultConfig { 7 | applicationId "com.xiaok.androidqzone" 8 | minSdkVersion 23 9 | targetSdkVersion 29 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | implementation 'androidx.appcompat:appcompat:1.0.2' 25 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 26 | testImplementation 'junit:junit:4.12' 27 | androidTestImplementation 'androidx.test.ext:junit:1.1.0' 28 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 29 | } 30 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/xiaok/androidqzone/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.xiaok.androidqzone; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.xiaok.androidqzone", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaok/androidqzone/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.xiaok.androidqzone; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | 7 | import androidx.appcompat.app.AppCompatActivity; 8 | 9 | public class MainActivity extends AppCompatActivity { 10 | 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | setContentView(R.layout.activity_main); 15 | 16 | findViewById(R.id.btn_go).setOnClickListener(new View.OnClickListener() { 17 | @Override 18 | public void onClick(View view) { 19 | startActivity(new Intent(MainActivity.this,VideoBrowerActivity.class)); 20 | } 21 | }); 22 | } 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaok/androidqzone/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.xiaok.androidqzone; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | public class MyApplication extends Application { 7 | public static Context mContext; 8 | 9 | @Override 10 | public void onCreate() { 11 | super.onCreate(); 12 | mContext = getApplicationContext(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaok/androidqzone/VideoBrower.java: -------------------------------------------------------------------------------- 1 | package com.xiaok.androidqzone; 2 | 3 | import java.io.Serializable; 4 | 5 | public class VideoBrower implements Serializable { 6 | 7 | private static final long serialVersionUID = 1L; 8 | 9 | private int avatarId; 10 | private String username; 11 | private String date; 12 | private String videoDescripation; 13 | private String videoPath; 14 | private String position; 15 | 16 | 17 | public VideoBrower(int avatarId, String username, String date, String videoDescripation, String videoPath, String position) { 18 | this.avatarId = avatarId; 19 | this.username = username; 20 | this.date = date; 21 | this.videoDescripation = videoDescripation; 22 | this.videoPath = videoPath; 23 | this.position = position; 24 | } 25 | 26 | public int getAvatarId() { 27 | return avatarId; 28 | } 29 | 30 | public String getUsername() { 31 | return username; 32 | } 33 | 34 | public String getDate() { 35 | return date; 36 | } 37 | 38 | public String getVideoDescripation() { 39 | return videoDescripation; 40 | 41 | } 42 | 43 | public String getVideoPath() { 44 | return videoPath; 45 | } 46 | 47 | public String getPosition() { 48 | return position; 49 | } 50 | 51 | public void setAvatarId(int avatarId) { 52 | this.avatarId = avatarId; 53 | } 54 | 55 | public void setDate(String date) { 56 | this.date = date; 57 | } 58 | 59 | public void setUsername(String username) { 60 | this.username = username; 61 | 62 | } 63 | 64 | public void setVideoDescripation(String videoDescripation) { 65 | this.videoDescripation = videoDescripation; 66 | } 67 | 68 | public void setVideoPath(String videoPath) { 69 | this.videoPath = videoPath; 70 | } 71 | 72 | public void setPosition(String position) { 73 | this.position = position; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaok/androidqzone/VideoBrowerActivity.java: -------------------------------------------------------------------------------- 1 | package com.xiaok.androidqzone; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.view.Gravity; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.widget.AdapterView; 9 | import android.widget.ImageButton; 10 | import android.widget.ListView; 11 | 12 | 13 | import java.util.LinkedList; 14 | import java.util.List; 15 | 16 | import androidx.appcompat.app.ActionBar; 17 | import androidx.appcompat.app.AppCompatActivity; 18 | 19 | public class VideoBrowerActivity extends AppCompatActivity { 20 | 21 | private List aDate; 22 | private int[]videoAvatars; 23 | private String[]usernames; 24 | private String[]videoDates; 25 | private String[]videoDescripation; 26 | private String[]videoPaths; 27 | private String[]videoPosition; 28 | 29 | private ListView lv_video; 30 | 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_video_brower); 35 | 36 | lv_video = findViewById(R.id.video_listview); 37 | 38 | 39 | videoAvatars = new int[]{R.drawable.head_picture,R.mipmap.video_avatar_02,R.mipmap.video_avatar_03,R.mipmap.video_avatar_04}; 40 | usernames = new String[]{"xiaok","小张","王小明","李晓华"}; 41 | videoDates = new String[]{"刚刚","09月17日10:12","08月22日18:15","08月22日17:42"}; 42 | videoDescripation = new String[]{"#测试1.1# 童年卢本伟","#测试1.2# 川普","#测试1.3# 李云龙","#测试1.4# 奥利给"}; 43 | videoPaths = new String[]{"https://www.bilibili.com/video/av80588467", 44 | "https://www.bilibili.com/video/av77275486?spm_id_from=333.5.b_6b696368696b755f6775696465.18", 45 | "https://www.bilibili.com/video/av80721495", 46 | "https://www.bilibili.com/video/av75889185?spm_id_from=333.5.b_6b696368696b755f6d6164.4"}; 47 | videoPosition = new String[]{"北京市朝阳区","郑州市中原区","郑州市中原区","郑州市中原区"}; 48 | 49 | aDate = new LinkedList<>(); 50 | for (int i=0;i)aDate,VideoBrowerActivity.this)); 56 | lv_video.setOnItemClickListener(new AdapterView.OnItemClickListener() { 57 | @Override 58 | public void onItemClick(AdapterView parent, View view, int position, long id) { 59 | 60 | } 61 | }); 62 | 63 | } 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaok/androidqzone/VideoBrowerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.xiaok.androidqzone; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.graphics.Bitmap; 6 | import android.graphics.drawable.BitmapDrawable; 7 | import android.graphics.drawable.Drawable; 8 | import android.media.MediaMetadataRetriever; 9 | import android.media.MediaPlayer; 10 | import android.net.Uri; 11 | import android.text.TextUtils; 12 | import android.view.LayoutInflater; 13 | import android.view.MotionEvent; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.widget.BaseAdapter; 17 | import android.widget.EditText; 18 | import android.widget.ImageView; 19 | import android.widget.LinearLayout; 20 | import android.widget.TextView; 21 | import android.widget.Toast; 22 | import android.widget.VideoView; 23 | 24 | 25 | import com.xiaok.androidqzone.custom.CircleImageView; 26 | 27 | import java.util.LinkedList; 28 | 29 | public class VideoBrowerAdapter extends BaseAdapter { 30 | 31 | private LinkedList aData; 32 | private Context mContext; 33 | private boolean isGood = false; 34 | private boolean isVideoPlaying = false; 35 | private int commentIndex = 4; 36 | 37 | private TextView thumpUpView; 38 | 39 | public VideoBrowerAdapter(LinkedList aData, Context mContext){ 40 | this.aData = aData; 41 | this.mContext = mContext; 42 | } 43 | @Override 44 | public int getCount(){ 45 | return aData.size(); 46 | } 47 | @Override 48 | public Object getItem(int position){ 49 | return null; 50 | } 51 | @Override 52 | public long getItemId(int position){ 53 | return position; 54 | } 55 | 56 | @SuppressLint("ClickableViewAccessibility") 57 | @Override 58 | public View getView(int position, View convertView, ViewGroup parent){ 59 | final ViewHolder holder; 60 | if (convertView==null){ 61 | convertView = LayoutInflater.from(mContext).inflate(R.layout.video_brower_item,parent,false); 62 | holder = new ViewHolder(); 63 | holder.videoAvatar = convertView.findViewById(R.id.video_avatar); 64 | holder.username = convertView.findViewById(R.id.video_username); 65 | holder.videoDate = convertView.findViewById(R.id.video_date); 66 | holder.videoDescripation = convertView.findViewById(R.id.video_descripation); 67 | holder.video_view = convertView.findViewById(R.id.video_view); 68 | holder.videoPosition = convertView.findViewById(R.id.video_position); 69 | holder.videoPlay = convertView.findViewById(R.id.video_play); 70 | holder.video_iv_good = convertView.findViewById(R.id.video_iv_good); 71 | holder.video_iv_comment = convertView.findViewById(R.id.video_iv_comment); 72 | holder.video_iv_share = convertView.findViewById(R.id.video_iv_share); 73 | holder.video_et_comment = convertView.findViewById(R.id.video_et_comment); 74 | 75 | holder.mContainer = convertView.findViewById(R.id.mContainer); //拿到布局,用于动态添加View 76 | convertView.setTag(holder); 77 | }else { 78 | holder = (ViewHolder)convertView.getTag(); 79 | } 80 | MediaMetadataRetriever media = new MediaMetadataRetriever(); 81 | Uri videoUri = Uri.parse(aData.get(position).getVideoPath()); 82 | media.setDataSource(mContext,videoUri); 83 | Bitmap bitmap = media.getFrameAtTime(); 84 | 85 | holder.videoAvatar.setImageResource(aData.get(position).getAvatarId()); 86 | holder.username.setText(aData.get(position).getUsername()); 87 | holder.videoDate.setText(aData.get(position).getDate()); 88 | holder.videoDescripation.setText(aData.get(position).getVideoDescripation()); 89 | holder.video_view.setBackground(new BitmapDrawable(bitmap)); 90 | holder.video_view.setVideoURI(videoUri); 91 | holder.videoPosition.setText(aData.get(position).getPosition()); 92 | 93 | /* 94 | *响应事件 95 | */ 96 | //播放视频按钮 97 | holder.videoPlay.setOnClickListener(new View.OnClickListener() { 98 | @Override 99 | public void onClick(View v) { 100 | if (holder.video_view != null){ 101 | if (!isVideoPlaying){ 102 | holder.videoPlay.setImageResource(R.mipmap.ic_record_stop); 103 | holder.video_view.start(); 104 | holder.video_view.setBackground(null); 105 | holder.video_view.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { 106 | @Override 107 | public void onCompletion(MediaPlayer mp) { 108 | holder.videoPlay.setImageResource(R.mipmap.ic_record_play); 109 | } 110 | }); 111 | }else { 112 | holder.video_view.stopPlayback(); 113 | holder.videoPlay.setImageResource(R.mipmap.ic_record_play); 114 | } 115 | 116 | } 117 | } 118 | }); 119 | 120 | //点赞 121 | holder.video_iv_good.setOnClickListener(new View.OnClickListener() { 122 | @Override 123 | public void onClick(View v) { 124 | if (!isGood){ 125 | holder.video_iv_good.setImageResource(R.mipmap.video_share_good_blue); 126 | addThumpUpView(holder); 127 | Toast.makeText(mContext, "点赞成功!", Toast.LENGTH_SHORT).show(); 128 | isGood = true; 129 | }else { 130 | holder.video_iv_good.setImageResource(R.mipmap.video_share_good); 131 | removeThumpUpView(holder); 132 | isGood = false; 133 | } 134 | 135 | } 136 | }); 137 | 138 | //评论图片按钮 139 | holder.video_iv_comment.setOnClickListener(new View.OnClickListener() { 140 | @Override 141 | public void onClick(View v) { 142 | //焦点移动到评论区 143 | holder.video_et_comment.requestFocus(); 144 | } 145 | }); 146 | 147 | //评论框右边发表图标 148 | holder.video_et_comment.setOnTouchListener(new View.OnTouchListener() { 149 | @Override 150 | public boolean onTouch(View v, MotionEvent event) { 151 | // et.getCompoundDrawables()得到一个长度为4的数组,分别表示左右上下四张图片 152 | Drawable drawable = holder.video_et_comment.getCompoundDrawables()[2]; 153 | //如果右边没有图片,不再处理 154 | if (drawable == null) 155 | return false; 156 | //如果不是按下事件,不再处理 157 | if (event.getAction() != MotionEvent.ACTION_UP) 158 | return false; 159 | if (event.getX() > holder.video_et_comment.getWidth() 160 | - holder.video_et_comment.getPaddingRight() 161 | - drawable.getIntrinsicWidth()){ 162 | //发表 163 | String commentStr = holder.video_et_comment.getText().toString().trim(); 164 | if (TextUtils.isEmpty(commentStr)){ 165 | Toast.makeText(mContext,"评论内容不能为空!",Toast.LENGTH_SHORT).show(); 166 | }else { 167 | addView(holder, commentStr); 168 | holder.video_et_comment.setText(""); //发表完评论后编辑框清空 169 | Toast.makeText(mContext,"发表成功!",Toast.LENGTH_SHORT).show(); 170 | } 171 | 172 | } 173 | return false; 174 | } 175 | }); 176 | 177 | 178 | return convertView; 179 | } 180 | static class ViewHolder{ 181 | CircleImageView videoAvatar; 182 | TextView username; 183 | TextView videoDate; 184 | TextView videoDescripation; 185 | VideoView video_view; 186 | TextView videoPosition; 187 | ImageView videoPlay; 188 | ImageView video_iv_good; 189 | ImageView video_iv_comment; 190 | ImageView video_iv_share; 191 | EditText video_et_comment; 192 | LinearLayout mContainer; 193 | } 194 | 195 | private void addView(ViewHolder holder, String commentStr){ 196 | TextView view = new TextView(mContext); 197 | LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 60); 198 | lp.setMargins(0,15,0,15); 199 | view.setTextSize(14); 200 | view.setTextColor(mContext.getColor(R.color.record_comment_text)); 201 | view.setText("xiaok:"+commentStr); 202 | int index = holder.mContainer.getChildCount(); 203 | holder.mContainer.addView(view,index-1,lp); 204 | } 205 | 206 | private void addThumpUpView(ViewHolder holder){ 207 | thumpUpView = new TextView(mContext); 208 | LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 60); 209 | lp.setMargins(0,15,0,15); 210 | thumpUpView.setTextSize(14); 211 | thumpUpView.setTextColor(mContext.getColor(R.color.record_comment_text)); 212 | thumpUpView.setText("xiaok 觉得很赞"); 213 | holder.mContainer.addView(thumpUpView,3,lp); 214 | } 215 | 216 | private void removeThumpUpView(ViewHolder holder){ 217 | holder.mContainer.removeViewAt(3); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaok/androidqzone/custom/CircleImageView.java: -------------------------------------------------------------------------------- 1 | package com.xiaok.androidqzone.custom; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapShader; 6 | import android.graphics.Canvas; 7 | import android.graphics.Matrix; 8 | import android.graphics.Paint; 9 | import android.graphics.Shader; 10 | import android.graphics.drawable.BitmapDrawable; 11 | import android.graphics.drawable.Drawable; 12 | import android.util.AttributeSet; 13 | 14 | import androidx.annotation.Nullable; 15 | import androidx.appcompat.widget.AppCompatImageView; 16 | 17 | public class CircleImageView extends AppCompatImageView { 18 | private float width; 19 | private float height; 20 | private float radius; 21 | private Paint paint; 22 | private Matrix matrix; 23 | 24 | public CircleImageView(Context context) { 25 | this(context, null); 26 | } 27 | 28 | public CircleImageView(Context context, @Nullable AttributeSet attrs) { 29 | this(context, attrs, 0); 30 | } 31 | 32 | public CircleImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 33 | super(context, attrs, defStyleAttr); 34 | paint = new Paint(); 35 | paint.setAntiAlias(true); //设置抗锯齿 36 | matrix = new Matrix(); //初始化缩放矩阵 37 | } 38 | 39 | /** 40 | * 测量控件的宽高,并获取其内切圆的半径 41 | */ 42 | @Override 43 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 44 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 45 | width = getMeasuredWidth(); 46 | height = getMeasuredHeight(); 47 | radius = Math.min(width, height) / 2; 48 | } 49 | 50 | @Override 51 | protected void onDraw(Canvas canvas) { 52 | Drawable drawable = getDrawable(); 53 | if (drawable == null) { 54 | super.onDraw(canvas); 55 | return; 56 | } 57 | if (drawable instanceof BitmapDrawable) { 58 | paint.setShader(initBitmapShader((BitmapDrawable) drawable));//将着色器设置给画笔 59 | canvas.drawCircle(width / 2, height / 2, radius, paint);//使用画笔在画布上画圆 60 | return; 61 | } 62 | super.onDraw(canvas); 63 | } 64 | 65 | /** 66 | * 获取ImageView中资源图片的Bitmap,利用Bitmap初始化图片着色器,通过缩放矩阵将原资源图片缩放到铺满整个绘制区域,避免边界填充 67 | */ 68 | private BitmapShader initBitmapShader(BitmapDrawable drawable) { 69 | Bitmap bitmap = drawable.getBitmap(); 70 | BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 71 | float scale = Math.max(width / bitmap.getWidth(), height / bitmap.getHeight()); 72 | matrix.setScale(scale, scale);//将图片宽高等比例缩放,避免拉伸 73 | bitmapShader.setLocalMatrix(matrix); 74 | return bitmapShader; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/head_picture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuaij/AndroidQzone/7eb9d5e0904de7a6c62027b48fbd50ae444b6270/app/src/main/res/drawable/head_picture.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/video_send_picture.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/video_share_send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuaij/AndroidQzone/7eb9d5e0904de7a6c62027b48fbd50ae444b6270/app/src/main/res/drawable/video_share_send.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 |