仿华为ViewPager指示器开发教程
首先自定义一个LinearLayout
MyLinearLayout:
package cn.zmit.myapplication;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.text.BoringLayout;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
/**
* Created by kyle on 2016/3/14.
*/
public class MyLinearLayout extends LinearLayout {
private int startX;//初始位置X坐标
private Paint mPaint;
private int moveX;//移动时不断变化的X坐标
public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setColor(Color.parseColor("#FFFFFF"));//画笔颜色
mPaint.setStyle(Paint.Style.FILL);//画笔样式(填充内部)
}
public MyLinearLayout(Context context) {
super(context, null);
}
/***
* 开始画圆
*
* @param canvas
*/
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
startX = getWidth() / 8;//最开始位置的X坐标
canvas.save();//保存
canvas.drawCircle(startX + moveX, getHeight() - 15, 5, mPaint);
canvas.restore();//取出
}
/***
* 当手指滑动时调用这个方法(在viewpager的onPageScrolled方法调用)
*
* @param position
* @param Offset
*/
public void changed(int position, float Offset) {
moveX = (int) (getWidth() / 4 * Offset + position * getWidth() / 4);
invalidate();//刷新
}
}
既然用到ViewPager,当然需要fragment;
MyFragment.java:
package cn.zmit.myapplication;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
/**
* Created by Administrator on 2016/3/14.
*/
public class MyFragment extends Fragment {
public static final String TITLE = "title";
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
TextView textView = new TextView(getActivity());
textView.setText(getArguments().getString(TITLE));
textView.setTextColor(Color.parseColor("#000000"));
textView.setGravity(Gravity.CENTER);
return textView;
}
public static MyFragment getInstance(String title) {
Bundle pBundle = new Bundle();
pBundle.putString(TITLE, title);
MyFragment fragment = new MyFragment();
fragment.setArguments(pBundle);
return fragment;
}
}
MainActivity.java:
package cn.zmit.myapplication;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.Window;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MainActivity extends FragmentActivity {
private MyLinearLayout mLinearLayout;
private ViewPager mViewpager;
private FragmentStatePagerAdapter adapter;
public List<String> lists = Arrays.asList("推荐", "排行", "分类","我的");
private List<MyFragment> list = new ArrayList<>();
private void assignViews() {
mLinearLayout = (MyLinearLayout) findViewById(R.id.linearLayout);
mViewpager = (ViewPager) findViewById(R.id.viewpager);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
assignViews();
initData();
}
private void initData() {
for (String title : lists) {
MyFragment fragment = MyFragment.getInstance(title);
list.add(fragment);
}
adapter = new FragmentStatePagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
return list.get(position);
}
@Override
public int getCount() {
return list.size();
}
};
mViewpager.setAdapter(adapter);
mViewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
mLinearLayout.changed(position, positionOffset);
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
}
好了,接下来开始讲一下原理。
这个例子最重要的地方,大家都能知道,在于自定义的LinearLayout,我们先从这里说起吧。
首先,
private int startX;//初始位置X坐标
private Paint mPaint;
private int moveX;//移动时不断变化的X坐标
startX为初始化原点的x坐标
maveX为当我们滑动ViewPager时动态改变的X数值。
然后,在构造方法中,初始画笔mPaint,设置颜色等属性。
接着,在dispatchDraw方法中执行画图功能,这个方法系统自动调用,主要是分发给子组件进行绘制。
代码解析:
startX = getWidth() / 8;//最开始位置的X坐标
初始化startX为屏幕的1/8,因为布局被分为4块,而原点又要位于每个模块的中间,所以初始的X应该为屏幕宽度的1/8。
canvas.save();//保存
canvas.drawCircle(startX + moveX, getHeight() - 15, 5, mPaint);
canvas.restore();//取出
sava和restore方法用于保存画布画图之前的状态和取出之前的状态,绘图时最好带上,这里不说了,有兴趣的同学可以去了解下。
drawCircle方法,顾名思义,画圆,第一个参数代表当前X坐标;第二个参数是高度,全程固定为布局的高度减去15,
大概在字和底部的中间。这个数值可以自己改,改到自己喜欢为止;第三个参数是半径,这里设为5,同理,可以根据喜好改;第四个参数是画笔,之前初始化完成了,这里直接加进去。
先看一下效果:
1
最后,重头戏来了,大家还记得在之前的ViewPager的addOnPageChangeListener里的onPageScrolled中做了什么吗?没错,在里面调用了一个方法:changed,让我们看一下,这个方法里做了什么
public void changed(int position, float Offset) {
moveX = (int) (getWidth() / 4 * Offset + position * getWidth() / 4);
invalidate();//刷新
}
看里面内容,将moveX的值变成了getWidth() / 4 * Offset + position * getWidth() / 4。
这到底是多少呢?先介绍一下两个参数,Offset,即滑动的宽度的百分比,比如我手指向左滑动,那么ViewPager的当前fragment也会随着手指往左边移动,当前fragment被隐藏的宽度占当前fragment的总宽度的百分比,即为Offset。Position就是当前fragment为第几个fragment,第一个的话,postion就为0,因为从0开始嘛!(注意,手指往左滑,position为当前position,手指往右滑,position为当前position-1)
介绍完参数后,开始说一下这个moveX到底变成了多少。
getWidth/4,屏幕宽度的1/4,即一个模块的宽度,乘以Offset,即随着手指滑动,使moveX的数值一直变大,直至正好为一个模块的宽,也就是从一个模块,滑到了另一个模块。听着已经可以了,但是不要忘记,Offset每次滑动后,都会变成0,也就是说,假设就这样完事的话,你只能将小圆点从第一个模块移动到第二个模块,无论你怎么滑。
好了,接着,后面还加了position * getWidth() / 4,即当前fragment的position乘以当前模块的宽,这样就解决了只能滑到第二个模块的问题,因为假设当前fragment为第二个,position为1,原点位于模块2,即“排行”,X的数值为X=startX+moveX;
startX=getWidth() / 8 ;
moveX=getWidth() / 4 * Offset + 1* getWidth() / 4;
当我们没动时,X的数值等于”排行”两字中间的X值,
X=getWidth() / 8+1* getWidth() / 4;
当我们往左滑动,Offset不断变大,直至1,position为1
X=getWidth() / 8+getWidth() / 4 +1* getWidth() / 4;
正好比没滑动时大一个模块,即滑到了第三个模块”分类”
当我们往右滑动,Offset不断变大,直至1,position为0
X=getWidth() / 8+getWidth() / 4 +0* getWidth() / 4;
正好比没滑动时小一个模块,即滑到了第一个模块”推荐”
效果图由于条件限制,没法录制gif,所以大家可以自己把代码运行一下看效果。
总结:这个例子主要是运用了viewGroup的画图功能,自定义了一个带圆点的LinearLayout,随后运用ViewPager的滑动参数,动态改变圆点位置。主要在于了解viewPager的运行机制,还有需要了解画笔的使用。
原文来自 :http://blog.it985.com/15824.html
直接贴代码
tvModel.setText(android.os.Build.MODEL);//手机型号
// 获取屏幕密度(方法1)
int screenWidth1 = getActivity().getWindowManager().getDefaultDisplay().getWidth(); // 屏幕宽(像素,如:480px)
int screenHeight1 = getActivity().getWindowManager().getDefaultDisplay().getHeight(); // 屏幕高(像素,如:800p)
tvScreenHeight1.setText(screenHeight1 + " px");
tvScreenWidth1.setText(screenWidth1 + " px");
// 获取屏幕密度(方法2)
DisplayMetrics dm2 = getResources().getDisplayMetrics();
float density = dm2.density; // 屏幕密度(像素比例:0.75/1.0/1.5/2.0)
int densityDPI = dm2.densityDpi; // 屏幕密度(每寸像素:120/160/240/320)
float xdpi = dm2.xdpi;
float ydpi = dm2.ydpi;
int screenWidth2 = dm2.widthPixels; // 屏幕宽(像素,如:480px)
int screenHeight2 = dm2.heightPixels; // 屏幕高(像素,如:800px)
tvScreenHeight2.setText(screenHeight2 + " px");
tvScreenWidth2.setText(screenWidth2 + " px");
tvScreenDensity2.setText(density + "");
tvScreenDensityDPI2.setText(densityDPI + "");
// 获取屏幕密度(方法3)
DisplayMetrics dm3 = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm3);
density = dm3.density; // 屏幕密度(像素比例:0.75/1.0/1.5/2.0)
densityDPI = dm3.densityDpi; // 屏幕密度(每寸像素:120/160/240/320)
xdpi = dm3.xdpi;
ydpi = dm3.ydpi;
tvScreenDensity3.setText(density + "");
tvScreenDensityDPI3.setText(densityDPI + "");
int screenWidth3 = dm3.widthPixels; // 屏幕宽(px,如:480px)
int screenHeight3 = dm3.heightPixels; // 屏幕高(px,如:800px)
tvScreenHeight3.setText(screenHeight3 + " px");
tvScreenWidth3.setText(screenWidth3 + " px");
float screenWidthDip = (dm3.widthPixels / density); // 屏幕宽(dip,如:320dip)
float screenHeightDip = (dm3.heightPixels / density); // 屏幕宽(dip,如:533dip)
tvScreenHeight4.setText(screenHeightDip + " dip");
tvScreenWidth4.setText(screenWidthDip + " dip");
我们使用ViewHolder时,把每一个item的子View控件对象都放在ViewHolder中,当第一次创建convertView对象时,便把这些item的子View控件对象findViewById实例化出来并保存到ViewHolder的对象中。然后用convertView的setTag将viewHolder对象设置到Tag中, 当以后加载ListView的item时便可以直接从Tag中取出复用ViewHolder对象中的,不需要再findViewById找item的子控件对象了。这样便大大提高了性能。
但是,某些情况下,这种优化技术便出现了一种问题,举个例子,当我们由于项目需要,需要在ListView中用到CheckBox时,那么,就会出现这样的情况:
假设一个ListView有10个Item,当我选择第一个Item的CheckBox之后,往下滑动到原先不可见的位置时就会发现,还有其他的CheckBox也被选中了,这样就造成了很不好的用户体验。
先上出现了这种问题的代码:
布局文件:
activity_listview.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
</LinearLayout>
item_list.xml 一个CheckBox和一个TextView,设为100dp高的原因是,必须有未显示的Item,ListView才会去复用View,如果一个屏幕显示了全部的Item,那么就不回复用了。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="100dp">
<CheckBox
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/textView"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
</LinearLayout>
MyAdapter.java
package cn.zmit.myapplication;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.List;
/**
* Created by Administrator on 2016/3/23.
*/
public class MyAdapter extends BaseAdapter {
private List<String> list;
private Context context;
public MyAdapter(List<String> lists, Context contexts) {
list = lists;
context = contexts;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = View.inflate(context, R.layout.item_list, null);
holder.textView = (TextView) convertView.findViewById(R.id.textView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.textView.setText(list.get(position));
return convertView;
}
private class ViewHolder {
TextView textView;
}
}
ListVeiwDemo.java
package cn.zmit.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Administrator on 2016/3/18.
*/
public class ListViewDemo extends Activity {
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_listview);
listView= (ListView) findViewById(R.id.listview);
List<String> list=new ArrayList<>();
for(int i=0;i<10;i++){
list.add(i+"");
}
MyAdapter adapter=new MyAdapter(list,this);
listView.setAdapter(adapter);
}
}
非常正常的一个ListView实现过程,贴代码的原因是担心有初学者不懂我在说什么。大家可以把代码拷贝一下或者自己编写看一下效果。
接下来说一下怎么解决!!!!
首先,定义一个Map,
Map<Integer,View>map=new HashMap<>();
当convertView为null时,在convertView和子控件加载完成后,将convertView加入map:
map.put(position,convertView);
我们想一想,这时候,在前面判断的时候,还应该是
if (convertView == null)
这样吗?当然不是,换成这样:
if (map.get(position)==null)
也就是说,只要你这个Item没被加载过,就给我重新加载!
然后在下面的else里这样写:
else {
convertView=map.get(position);
holder = (ViewHolder) convertView.getTag();
}
假设map里有这个View,就拿出来用。
全部解决完问题的Adapter的getView部分代码为:
Map<Integer,View>map=new HashMap<>();
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (map.get(position)==null) {
holder = new ViewHolder();
convertView = View.inflate(context, R.layout.item_list, null);
holder.textView = (TextView) convertView.findViewById(R.id.textView);
map.put(position,convertView);
convertView.setTag(holder);
} else {
convertView=map.get(position);
holder = (ViewHolder) convertView.getTag();
}
holder.textView.setText(list.get(position));
return convertView;
}
android从选择图片有两种方法,但是返回值确不同,本文将指导大家如何统一这两种方式的返回值。
//关键代码
@Event(R.id.btnPhoto)
private void onBtnPhotoClicked(View view) {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, Config.Constants.CODE_PICK_IMAGE_FROM_PHOTO);
}
@Event(R.id.btnCamera)
private void onBtnCameraClicked(View view) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, Config.Constants.CODE_PICK_IMAGE_FROM_CAMERA);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case Config.Constants.CODE_PICK_IMAGE_FROM_CAMERA:
if (data != null && data.hasExtra("data")) {
Bitmap bitmap = data.getParcelableExtra("data");
bitmap = BitmapUtil.scale(bitmap, 640.0f / bitmap.getWidth());
try {
File path = new File(((MyApplication) getApplication()).appPath, DateUtil.format(new Date(), "yyyyMMddHHmmss") + ".jpg");
FileOutputStream outputStream = new FileOutputStream(path);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
outputStream.close();
Intent intent = new Intent();
intent.putExtra("path", path.getAbsolutePath());
setResult(RESULT_OK, intent);
} catch (IOException e) {
e.printStackTrace();
}
}
finish();
break;
case Config.Constants.CODE_PICK_IMAGE_FROM_PHOTO:
if(data != null){
Uri uri = data.getData();
Bitmap bitmap;
ContentResolver contentResolver = getContentResolver();
try {
bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri);
bitmap = BitmapUtil.scale(bitmap, 640.0f / bitmap.getWidth());
File path = new File(((MyApplication) getApplication()).appPath, DateUtil.format(new Date(), "yyyyMMddHHmmss") + ".jpg");
FileOutputStream outputStream = new FileOutputStream(path);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
outputStream.close();
Intent intent = new Intent();
intent.putExtra("path", path.getAbsolutePath());
setResult(RESULT_OK, intent);
} catch (IOException e) {
e.printStackTrace();
}
}
finish();
break;
default:
super.onActivityResult(requestCode, resultCode, data);
}
}
//BitmapUtil.java
public static Bitmap scale(Bitmap bitmap, float scale) {
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
//MyApplication
public File appPath;
@Override
public void onCreate() {
super.onCreate();
//创建目录
appPath = new File(Environment.getExternalStorageDirectory(), getPackageName());
if (!appPath.isDirectory()) {
appPath.mkdir();
}
}
经过统一处理之后,返回值均为图片的绝对路径地址。
华为P9什么时候出?
P9发布会是4月6日。价格方面标准版 3088 元,高配版 3688 元
华为P9手机配置怎么样?
华为“??”图案正暗示其双镜头的设计。据悉将配备 5.2 ? 1080p 屏幕,麒麟 950,3G / 4G 内存,32GB 存储,1200W 双摄像头。价格方面标准版 3088 元,高配版 3688 元
华为P9采用双摄像头设计,并配备华为自主研发的传感器组合技术,支持重调焦距、模拟光圈调整、滤波镜应用等。由于其摄影光学系统是由德国著名光学品牌徕卡来调教,所以还会在机身上标记徕卡的“可乐标”
有网友曝光了一组P9以及保护套的渲染图,从图中来看,和之前曝光的一样,P9采用了背后双摄像头,配备双LED闪光灯。此外,指纹识别模块也同样设计在了背部。
手机侧面采用了金属中框,视觉上看起来很薄,背壳则是全金属材质。
相关文章
- 今天小编在这里就来给Painter的这一款软件的使用者们来说一说绘制红衣喝酒男水粉画效果的教程,各位想知道具体绘制步骤的使用者,那么下面就快来跟着小编一起看一看教程...2016-09-14
iPhone6怎么激活?两种苹果iPhone6激活教程图文详解
iPhone6新机需要激活后才可以正常使用,那么对于小白用户来说,iPhone6如何激活使用呢?针对此问题,本文就为大家分别介绍Wifi无线网络激活以及iPhone6连接电脑激活这两种有效的方法,希望本文能够帮助到大家...2022-09-14- 今天小编在这里就来给各位photoshop的这一款软件的使用者们来说下制作雨中野外孤独行走的一头牛海报的教程,各位想知道具体制作方法的使用者们,大家就快来看一看小编给...2016-09-14
- 今天小编在这里就来给Painter的这一款软件的使用者们来说一下绘制帅气卡通魔法王子漫画的具体教程,各位想知道绘制步骤的使用者,那么下面就快来跟着小编一起看一看教程...2016-09-14
- 今天小编在这里就来给各位Illustrator的这一款软件的使用者们来说说鼠绘堆雪人的孩童矢量插画的教程,各位想知道具体绘制方法的使用者们,那么各位就快来跟着小编来看看...2016-09-14
- 支付宝支付在国内算是大家了,我们到处都可以使用支付宝了,下文整理介绍的是在安卓app应用中使用支付宝进行支付的开发例子。 之前讲了一篇博客关与支付宝集成获取...2016-09-20
- 今天小编在这里就来给美图秀秀的这一款软件的使用者们来说下究竟该怎么给照片天空加蓝天白云的教程,各位想知道具体制作步骤的,那么下面就来跟着小编一起看看吧。 ...2016-09-14
- 今天小编在这里就来给llustrator的这一款软件的使用者们来说一说绘制扁平化风格卡通警察护士空姐肖像的教程,各位想知道具体绘制步骤的使用者们,那么下面就快来跟着小编...2016-09-14
- 今天小编在这里就来给photoshop的这一款软件的使用者们来说一说简单制作一个搞笑的换脸表情包的教程,各位想知道具体制作方法的使用者们,那么大家就快来看一看教程吧。...2016-09-14
- 今天小编在这里就来给Illustrator的这一款软件的使用者们来说一下绘制一个方形的录音机图标的教程,各位想知道具体绘制方法的使用者们,那么下面就来看一下小编给大家分...2016-09-14
- 今天小编在这里就来给各位photoshop的这一款软件的使用者们来说说给手绘画调色变换场景的后期教程,各位想知道具体后期处理步骤的使用者们,那么大家就快来跟着小编来看...2016-10-02
- 今天小编在这里就来给美图秀秀的这一款软件的使用者们来说一下让你胸丰满起来的处理教程,各位想知道具体处理步骤的,那么下面就快来跟着小编一起看一下教程吧。 给...2016-09-14
- 今天小编在这里就来给Painter的这一款软件的使用者们来说一下绘制雷神传插画的教程,各位想知道具体绘制步骤的使用者,那么下面就快来跟着小编一起看看绘制方法吧。 ...2016-09-14
- 联合索引又叫复合索引。对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分。例如索引是key index (a,b,c). 可以支持a | a,b| a,b,c 3种组合进行查找,但不支持 b,c进...2015-11-24
- 这篇文章主要给大家介绍的是关于Lua语言新手入门的简单教程,文中通过示例代码一步步介绍的非常详细,对各位新手们的入门提供了一个很方便的教程,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧。...2020-06-30
- 一、下载 mysqlsla [root@localhost tmp]# wget http://hackmysql.com/scripts/mysqlsla-2.03.tar.gz--19:45:45-- http://hackmysql.com/scripts/mysqlsla-2.03.tar.gzResolving hackmysql.com... 64.13.232.157Conn...2015-11-24
- php类的使用实例教程 <?php /** * Class program for yinghua05-2 * designer :songsong */ class Template { var $tpl_vars; var $tpl_path; var $_deb...2016-11-25
- 今天小编在这里就来给美图秀秀的这一款软件的使用者们来说下制作隔离区聊天背景的教程,各位想知道具体方法的,那么下面就快来跟着小编一起看一看吧。 给各位美图秀...2016-09-14
- 今天小编在这里就来给photoshop的这一款软件的使用者们来说说用素材合成古典园林场景教程,各位想知道到底该怎么制作的,那么就快来一起看一下吧。 给各位photoshop...2016-09-14
- 这篇文章主要介绍了vscode搭建STM32开发环境的详细过程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-05-02